如何很好地处理`打开(…)`和`sys.stdout`?
通常我需要将数据输出到文件,或者如果文件没有指定,则输出到stdout。 我使用下面的代码片段:
if target: with open(target, 'w') as h: h.write(content) else: sys.stdout.write(content)
我想重写它,并统一处理这两个目标。
在理想的情况下,这将是:
with open(target, 'w') as h: h.write(content)
但是这不能很好的工作,因为sys.stdout是封闭的时候closures,我不想这样做。 我既不想
stdout = open(target, 'w') ...
因为我需要记住恢复原始的标准输出。
有关:
- 将stdoutredirect到Python中的文件?
- 处理exception – 与C ++相比,关于处理Python exception的有趣文章
编辑
我知道我可以包装target
,定义单独的function或使用上下文pipe理器 。 我寻找一种简单,优雅,惯用的解决scheme,不需要超过5条线
只是在这个盒子外面思考,一个自定义的open()
方法呢?
import sys import contextlib @contextlib.contextmanager def smart_open(filename=None): if filename and filename != '-': fh = open(filename, 'w') else: fh = sys.stdout try: yield fh finally: if fh is not sys.stdout: fh.close()
像这样使用它:
# writes to some_file with smart_open('some_file') as fh: print >>fh, 'some output' # writes to stdout with smart_open() as fh: print >>fh, 'some output' # writes to stdout with smart_open('-') as fh: print >>fh, 'some output'
坚持你当前的代码。 这很简单,你可以通过一眼就知道它究竟在做什么。
另一种方法是与内联, if
:
handle = open(target, 'w') if target else sys.stdout handle.write(content) if handle is not sys.stdout: handle.close()
但是这并不比你有的短得多,而且看起来可能更糟。
你也可以使sys.stdout
不可closures,但这似乎不太Pythonic:
sys.stdout.close = lambda: None with (open(target, 'w') if target else sys.stdout) as handle: handle.write(content)
为什么LBYL当EAFP?
try: with open(target, 'w') as h: h.write(content) except TypeError: sys.stdout.write(content)
为什么要重写它,以便让它以一种复杂的方式工作时,一致地使用with
/ as
块? 您将添加更多行并降低性能。
我也会去做一个简单的包装函数,如果你可以忽略模式(因此stdin和stdout),那么这个函数可以非常简单,例如:
from contextlib import contextmanager import sys @contextmanager def open_or_stdout(filename): if filename != '-': with open(filename, 'w') as f: yield f else: yield sys.stdout
好吧,如果我们进入单线战争,这里是:
(target and open(target, 'w') or sys.stdout).write(content)
只要上下文只写在一个地方,我就喜欢雅各布最初的例子。 这将是一个问题,如果你最终重新打开文件的许多写道。 我想我只是在脚本的顶部做一次决定,让系统在退出时closures文件:
output = target and open(target, 'w') or sys.stdout ... output.write('thing one\n') ... output.write('thing two\n')
如果你觉得它更整洁,你可以包括你自己的退出处理程序
import atexit def cleanup_output(): global output if output is not sys.stdout: output.close() atexit(cleanup_output)
另一个可能的解决scheme:不要试图避免上下文pipe理器退出方法,只是复制标准输出。
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w') if target == '-' else open(target, 'w')) as f: f.write("Foo")
如何打开一个新的fd sys.stdout? 这样你就不会有任何问题了:
if not target: target = "/dev/stdout" with open(target, 'w') as f: f.write(content)
如果你真的要坚持一些更“优雅”的东西,
>>> import sys >>> target = "foo.txt" >>> content = "foo" >>> (lambda target, content: (lambda target, content: filter(lambda h: not h.write(content), (target,))[0].close())(open(target, 'w'), content) if target else sys.stdout.write(content))(target, content)
出现foo.txt
并包含文本foo
。
if (out != sys.stdout): with open(out, 'wb') as f: f.write(data) else: out.write(data)
在某些情况下略有改善。
沃尔夫答案的改进
import sys @contextlib.contextmanager def smart_open(filename: str = None, mode: str = 'r', *args, **kwargs): if filename == '-': if 'r' in mode: stream = sys.stdin else: stream = sys.stdout if 'b' in mode: fh = stream.buffer else: fh = stream else: fh = open(filename, mode, *args, **kwargs) try: yield fh finally: try: fh.close() except AttributeError: pass
这允许二进制IO,并传递最终的无关参数,如果filename
确实是一个文件名。