如何很好地处理`open(...)`和`sys.stdout`?

Jak*_* M. 84 python

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

if target:
    with open(target, 'w') as h:
        h.write(content)
else:
    sys.stdout.write(content)
Run Code Online (Sandbox Code Playgroud)

我想重写它并统一处理两个目标.

在理想的情况下,它将是:

with open(target, 'w') as h:
    h.write(content)
Run Code Online (Sandbox Code Playgroud)

但这不会很好,因为离开with块时sys.stdout被关闭,我不想这样做.我不想

stdout = open(target, 'w')
...
Run Code Online (Sandbox Code Playgroud)

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

有关:

编辑

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

Wol*_*lph 84

只是在这里开箱即用,自定义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()
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

# 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'
Run Code Online (Sandbox Code Playgroud)


Ble*_*der 26

坚持使用您当前的代码.这很简单,你可以通过浏览它来准确地告诉它正在做什么.

另一种方式是使用内联if:

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

if handle is not sys.stdout:
    handle.close()
Run Code Online (Sandbox Code Playgroud)

但这并不比你拥有的短得多,看起来可能更糟.

你也可以使用sys.stdoutunclosable,但这似乎不太Pythonic:

sys.stdout.close = lambda: None

with (open(target, 'w') if target else sys.stdout) as handle:
    handle.write(content)
Run Code Online (Sandbox Code Playgroud)

  • 我在投票赞成“离开它,你可以确切地知道它在做什么”和投票反对可怕的不可关闭的建议之间挣扎! (3认同)
  • 您可以通过为它创建上下文管理器来保持不可关闭性,只要您需要它:`with unclosable(sys.stdout): ...` 通过在此上下文中设置 `sys.stdout.close = lambda: None` manager 并在之后将其重置为旧值。但这似乎有点太牵强了...... (2认同)
  • @GreenAsJade我不认为他是_建议_使`sys.stdout`不可关闭,只是指出它可以完成。最好展示糟糕的想法并解释它们为什么不好,而不是不提及它们并希望它们不会被其他人偶然发现。 (2认同)

bhd*_*dnx 12

正如Python 中的条件 with 语句中所指出的,Python 3.7 允许使用contextlib.nullcontext

from contextlib import nullcontext

with open(target, "w") if target else nullcontext(sys.stdout) as f:
    f.write(content)
Run Code Online (Sandbox Code Playgroud)


2rs*_*2ts 7

为什么LBYL你可以EAFP?

try:
    with open(target, 'w') as h:
        h.write(content)
except TypeError:
    sys.stdout.write(content)
Run Code Online (Sandbox Code Playgroud)

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

  • @JakubM.在Python中,异常可以这样使用. (26认同)
  • 考虑到Python的`for`循环通过捕获它迭代的迭代器抛出的StopIteration错误而退出,我会说使用流控制的异常完全是Pythonic. (12认同)
  • 异常*不应该*用于控制例程的"正常"流程.性能?如果/其他的话,冒泡错误的速度会更快吗? (3认同)
  • 取决于您将使用另一个的可能性。 (2认同)

Evp*_*pok 6

沃尔夫的答案的改进

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
Run Code Online (Sandbox Code Playgroud)

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


Oli*_*ert 5

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

with (os.fdopen(os.dup(sys.stdout.fileno()), 'w')
      if target == '-'
      else open(target, 'w')) as f:
      f.write("Foo")
Run Code Online (Sandbox Code Playgroud)


Ste*_*aan 5

如果可以在 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")
Run Code Online (Sandbox Code Playgroud)

或者更一般地说:

with target if isinstance(target, io.IOBase) else open(target, "w") as f:
    f.write("hello world")
Run Code Online (Sandbox Code Playgroud)