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

Python 如何很好地处理`with open(…)`和`sys.stdout`?,python,Python,通常,我需要将数据输出到文件,或者,如果未指定文件,则输出到标准输出。我使用以下代码段: 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在

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

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在离开带有块的
时是关闭的,我不希望这样。我也不想

stdout = open(target, 'w')
...
因为我需要记住恢复原来的标准输出

相关:

  • -关于在Python中处理异常(与C相比)的有趣文章++
编辑


我知道我可以包装
目标
,定义单独的功能或使用。我在寻找一个简单、优雅、惯用的解决方案配件,它不需要超过5行

当您可以EAFP时为什么选择LBYL

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

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

只需跳出框框思考一下,一个自定义的
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)

坚持使用当前代码。它很简单,你只要看一眼就可以知道它在做什么

另一种方法是使用内联
,如果

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

if handle is not sys.stdout:
    handle.close()
但这并不比你所拥有的短多少,而且看起来更糟

您还可以使
sys.stdout
不可关闭,但这似乎不太像python:

sys.stdout.close = lambda: None

with (open(target, 'w') if target else sys.stdout) as handle:
    handle.write(content)

我还想使用一个简单的包装器函数,如果您可以忽略模式(从而忽略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

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

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

好的,如果我们陷入一轮战争,这里是:

(target and open(target, 'w') or sys.stdout).write(content)
我喜欢雅各布最初的例子,只要上下文只写在一个地方。如果您最终重新打开文件进行多次写入,这将是一个问题。我想我只需在脚本顶部做一次决定,然后让系统在退出时关闭文件:

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)

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

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

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

if not target:
    target = "/dev/stdout"
with open(target, 'w') as f:
    f.write(content)
在某些情况下略有改善。

如果
filename
确实是一个文件名,则允许二进制IO并将最终的无关参数传递给
open

import contextlib
导入系统
将contextlib.ExitStack()作为堆栈:
h=堆栈。如果target else sys.stdout,则输入_上下文(open(target,'w'))
h、 写作(内容)

如果您使用的是Python 3.3或更高版本,则只需另外两行:一行用于额外的
导入
,一行用于
堆栈。输入上下文

,如果
sys.stdout
带有
主体后关闭,您也可以使用如下模式:

# 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)
#目标为“-”时使用标准输出
如果目标为打开(目标为“w”)=“-”else sys.stdout作为f:
f、 写(“你好,世界”)
#目标为falsy时使用标准输出(无,空字符串,…)
如果目标else sys.stdout为f,则打开(目标,“w”):
f、 写(“你好,世界”)
或者更一般地说:

如果isinstance(target,io.IOBase)打开(target,“w”)作为f,则使用target
:
f、 写(“你好,世界”)

下面的解决方案不是一种美,而是很久很久以前的;就在…之前

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

异常不应用于控制例程的“正常”流。演出冒泡出一个错误会比if/else更快吗?取决于你使用其中一个的概率。@JakubM。在Python中,异常可以、应该、也可以像这样使用。考虑到Python的
for
循环是通过捕获迭代器抛出的StopIteration错误而退出的,我认为将异常用于流控制完全是Python式的。假设在使用sys.stdout时
target
None
,您需要捕获
TypeError
而不是
IOError
。可惜您没有在前面添加编辑;)无论如何或者,您也不必费心清理打开的文件:py您可以通过为其创建上下文管理器来保持其不可关闭性,只要您需要即可:
with unclosable(sys.stdout):…
通过在此上下文管理器中设置
sys.stdout.close=lambda:None
,然后将其重置为旧值。但这似乎有点太牵强了……我在投票赞成“别管它,你可以确切地知道它在做什么”和投票反对这个可怕的不可撤销的建议之间左右为难@GreenAsJade我不认为他是在建议将
sys.stdout
设置为不可关闭,只是指出这是可以做到的。与其不提及错误想法并希望它们不会被其他人偶然发现,不如展示错误想法并解释其原因。此解决方案不会在with子句的正常或错误终止时显式关闭文件,因此它不是一个上下文管理器。实现enter和exit的类是一个更好的选择。如果我试图用open\u或stdout(..)写入
块之外的
,我会得到
ValueError:I/O操作。我错过了什么?sys.stdout并不意味着要关闭。我认为您的一行程序不会关闭文件对象。我错了吗?@2rs2ts-是的。。。有条件地。文件对象的refcount变为零,因为没有指向它的变量,所以可以立即(在cpython中)调用它的_del__方法,或者在垃圾收集发生时稍后调用它。文档中有警告不要相信这会一直起作用,但我总是在较短的脚本中使用它。一种运行时间长,打开很多门的大东西
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()