Python 如何很好地处理`with open(...)` 和`sys.stdout`?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17602878/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle both `with open(...)` and `sys.stdout` nicely?
提问by Jakub M.
Often I need to output data either to file or, if file is not specified, to stdout. I use the following snippet:
通常我需要将数据输出到文件,或者如果未指定文件,则输出到标准输出。我使用以下代码段:
if target:
with open(target, 'w') as h:
h.write(content)
else:
sys.stdout.write(content)
I would like to rewrite it and handle both targets uniformly.
我想重写它并统一处理两个目标。
In ideal case it would be:
在理想情况下,它将是:
with open(target, 'w') as h:
h.write(content)
but this will not work well because sys.stdout is be closed when leaving with
block and I don't want that. I neither want to
但这不会很好地工作,因为 sys.stdout 在离开with
块时被关闭,我不希望那样。我既不想
stdout = open(target, 'w')
...
because I would need to remember to restore original stdout.
因为我需要记住恢复原始标准输出。
Related:
有关的:
- Redirect stdout to a file in Python?
- Handling Exceptions- interesting article about handling exceptions in Python, as compared to C++
- 将标准输出重定向到 Python 中的文件?
- 处理异常- 与 C++ 相比,关于在 Python 中处理异常的有趣文章
Edit
编辑
I know that I can wrap target
, define separate function or use context manager. I look for a simple, elegant, idiomatic solution fitting that wouldn't require more than 5 lines
我知道我可以包装target
、定义单独的函数或使用上下文管理器。我寻找一个简单、优雅、惯用的解决方案,不需要超过 5 行
采纳答案by Wolph
Just thinking outside of the box here, how about a custom open()
method?
在这里跳出框框思考,自定义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()
Use it like this:
像这样使用它:
# For Python 2 you need this line
from __future__ import print_function
# writes to some_file
with smart_open('some_file') as fh:
print('some output', file=fh)
# writes to stdout
with smart_open() as fh:
print('some output', file=fh)
# writes to stdout
with smart_open('-') as fh:
print('some output', file=fh)
回答by 2rs2ts
Why LBYL when you can EAFP?
当您可以 EAFP 时,为什么选择 LBYL?
try:
with open(target, 'w') as h:
h.write(content)
except TypeError:
sys.stdout.write(content)
Why rewrite it to use the with
/as
block uniformly when you have to make it work in a convoluted way? You'll add morelines and reduce performance.
当您必须使其以复杂的方式工作时,为什么要重写它以统一使用with
/as
块?您将添加更多行并降低性能。
回答by Blender
Stick with your current code. It's simple and you can tell exactlywhat it's doing just by glancing at it.
坚持使用您当前的代码。这很简单,您只需看一眼就可以准确地知道它在做什么。
Another way would be with an inline if
:
另一种方法是使用 inline if
:
handle = open(target, 'w') if target else sys.stdout
handle.write(content)
if handle is not sys.stdout:
handle.close()
But that isn't much shorter than what you have and it looks arguably worse.
但这并不比你拥有的短多少,而且看起来可以说更糟。
You could also make sys.stdout
unclosable, but that doesn't seem too Pythonic:
您也可以使sys.stdout
uncloseable,但这似乎不太 Pythonic:
sys.stdout.close = lambda: None
with (open(target, 'w') if target else sys.stdout) as handle:
handle.write(content)
回答by Tommi Komulainen
I'd also go for a simple wrapper function, which can be pretty simple if you can ignore the mode (and consequently stdin vs. stdout), for example:
我还会选择一个简单的包装函数,如果您可以忽略模式(以及因此 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
回答by 2rs2ts
If you really must insist on something more "elegant", i.e. a one-liner:
如果你真的必须坚持更“优雅”的东西,即单线:
>>> 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
appears and contains the text foo
.
foo.txt
出现并包含文本foo
。
回答by tdelaney
Okay, if we are getting into one-liner wars, here's:
好的,如果我们陷入单线War,这里是:
(target and open(target, 'w') or sys.stdout).write(content)
I like Jacob's original example as long as context is only written in one place. It would be a problem if you end up re-opening the file for many writes. I think I would just make the decision once at the top of the script and let the system close the file on exit:
我喜欢 Jacob 的原始例子,只要上下文只写在一个地方。如果您最终重新打开文件进行多次写入,这将是一个问题。我想我只会在脚本的顶部做出一次决定,让系统在退出时关闭文件:
output = target and open(target, 'w') or sys.stdout
...
output.write('thing one\n')
...
output.write('thing two\n')
You could include your own exit handler if you think its more tidy
如果您认为它更整洁,您可以包含自己的退出处理程序
import atexit
def cleanup_output():
global output
if output is not sys.stdout:
output.close()
atexit(cleanup_output)
回答by Olivier Aubert
Another possible solution: do not try to avoid the context manager exit method, just duplicate stdout.
另一种可能的解决方案:不要试图避免上下文管理器退出方法,只需复制标准输出。
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w')
if target == '-'
else open(target, 'w')) as f:
f.write("Foo")
回答by user2602746
How about opening a new fd for sys.stdout? This way you won't have any problems closing it:
为 sys.stdout 打开一个新的 fd 怎么样?这样你就不会在关闭它时遇到任何问题:
if not target:
target = "/dev/stdout"
with open(target, 'w') as f:
f.write(content)
回答by Eugene K
if (out != sys.stdout):
with open(out, 'wb') as f:
f.write(data)
else:
out.write(data)
Slight improvement in some cases.
在某些情况下略有改善。
回答by Evpok
An improvement of Wolph's answer
沃尔夫的答案的改进
import sys
import contextlib
@contextlib.contextmanager
def smart_open(filename: str, mode: str = 'r', *args, **kwargs):
'''Open files and i/o streams transparently.'''
if filename == '-':
if 'r' in mode:
stream = sys.stdin
else:
stream = sys.stdout
if 'b' in mode:
fh = stream.buffer # type: IO
else:
fh = stream
close = False
else:
fh = open(filename, mode, *args, **kwargs)
close = True
try:
yield fh
finally:
if close:
try:
fh.close()
except AttributeError:
pass
This allows binary IO and pass eventual extraneous arguments to open
if filename
is indeed a file name.
这允许二进制 IO 并将最终无关的参数传递给open
iffilename
确实是一个文件名。