如何很好地处理`打开(…)`和`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确实是一个文件名。