110

通常我需要将数据输出到文件,或者如果未指定文件,则输出到标准输出。我使用以下代码段:

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 在离开with块时被关闭,我不希望那样。我也不想

stdout = open(target, 'w')
...

因为我需要记住恢复原始标准输出。

有关的:

编辑

我知道我可以包装target、定义单独的函数或使用上下文管理器。我寻找一个简单、优雅、惯用的解决方案,不需要超过 5 行

4

13 回答 13

108

只是在这里跳出框框思考,自定义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()

像这样使用它:

# 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)
于 2013-07-11T20:35:51.137 回答
35

坚持使用您当前的代码。这很简单,您只需看一眼就可以准确地知道它在做什么。

另一种方法是使用 inline if

handle = open(target, 'w') if target else sys.stdout
handle.write(content)

if handle is not sys.stdout:
    handle.close()

但这并不比你拥有的短很多,而且看起来可能更糟。

你也可以使sys.stdout不可关闭,但这似乎不太Pythonic:

sys.stdout.close = lambda: None

with (open(target, 'w') if target else sys.stdout) as handle:
    handle.write(content)
于 2013-07-11T20:37:29.697 回答
10

当你可以 EAFP 时为什么选择 LBYL?

try:
    with open(target, 'w') as h:
        h.write(content)
except TypeError:
    sys.stdout.write(content)

当您必须使其以复杂的方式工作时,为什么要重写它以统一使用with/块?as您将添加更多行并降低性能。

于 2013-07-11T20:35:26.013 回答
8

沃尔夫答案的改进

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

这允许二进制 IO 并将最终无关的参数传递给openiffilename确实是一个文件名。

于 2017-08-17T12:50:06.117 回答
5

另一种可能的解决方案:不要试图避免上下文管理器退出方法,只需复制标准输出。

with (os.fdopen(os.dup(sys.stdout.fileno()), 'w')
      if target == '-'
      else open(target, 'w')) as f:
      f.write("Foo")
于 2014-09-30T13:49:51.713 回答
1

我也会选择一个简单的包装函数,如果你可以忽略模式(以及因此标准输入与标准输出),这可能非常简单,例如:

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
于 2013-07-11T21:26:12.840 回答
1

好吧,如果我们要进入单线战争,这里是:

(target and open(target, 'w') or sys.stdout).write(content)

只要上下文只写在一个地方,我就喜欢 Jacob 的原始示例。如果您最终重新打开文件进行多次写入,这将是一个问题。我想我会在脚本顶部做出一次决定,让系统在退出时关闭文件:

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)
于 2013-07-11T21:58:09.103 回答
1
import contextlib
import sys

with contextlib.ExitStack() as stack:
    h = stack.enter_context(open(target, 'w')) if target else sys.stdout
    h.write(content)

如果您使用的是 Python 3.3 或更高版本,则只需多出两行:一行用于 extra import,另一行用于stack.enter_context.

于 2020-05-11T21:11:47.040 回答
1

如果在 bodysys.stdout之后关闭就可以了with,您还可以使用这样的模式:

# Use stdout when target is "-"
with open(target, "w") if target != "-" else sys.stdout as f:
    f.write("hello world")

# Use stdout when target is falsy (None, empty string, ...)
with open(target, "w") if target else sys.stdout as f:
    f.write("hello world")

或者更一般地说:

with target if isinstance(target, io.IOBase) else open(target, "w") as f:
    f.write("hello world")
于 2020-09-10T15:24:07.930 回答
0

如果您真的必须坚持更“优雅”的东西,即单线:

>>> 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

于 2013-07-11T21:45:27.690 回答
0

为 sys.stdout 打开一个新的 fd 怎么样?这样你在关闭它时不会有任何问题:

if not target:
    target = "/dev/stdout"
with open(target, 'w') as f:
    f.write(content)
于 2014-12-09T09:31:01.147 回答
0
if (out != sys.stdout):
    with open(out, 'wb') as f:
        f.write(data)
else:
    out.write(data)

在某些情况下略有改善。

于 2015-11-17T05:23:26.623 回答
0

以下解决方案不是美女,而是来自很久很久以前;就在之前...

handler = open(path, mode = 'a') if path else sys.stdout
try:
    print('stuff', file = handler)
    ... # other stuff or more writes/prints, etc.
except Exception as e:
    if not (path is None): handler.close()
    raise e
handler.close()
于 2020-11-20T14:26:06.990 回答