【问题标题】:reopening sys.stdout after it is closed by with statement在被 with 语句关闭后重新打开 sys.stdout
【发布时间】:2016-11-27 07:58:26
【问题描述】:

我在使用 PyYAML 从 yaml 文件输入的打印信息时遇到问题。我正在尝试减少行数,而不影响功能。在某些运行中,输出必须附加到文件中,在其他运行中必须附加到标准输出。

一开始我在函数processData中多次使用了这个:

    if logName:
        fp = open(logName, 'a')
    else:
        fp = sys.stdout
    print(........, file=fp)
    print(........, file=fp)
    if logName:
        fp.close()

这行得通,但缺点是在出现问题时不使用 with 语句。

实际的问题不是复杂的打印语句,而是我自己

1) 打印到文件或 sys.stdout 时不想重复代码
2) 想要使用 with 语句,以便在出现打印错误时关闭文件
3)有几个这样的块,我不想为每个块调用不同的函数,从而防止代码重复

然后我尝试的是:

def processData(yamlData, logName=None):
    ......
    with open(logName, 'a') if logName else sys.stdout as fp:
        print(........, file=fp)
        print(........, file=fp)
    .....
    with open(logName, 'a') if logName else sys.stdout as fp:
        print(........, file=fp)
        print(........, file=fp)

如果没有 logName,则此错误为“ValueError: I/O operation on closed file”。关于如何在没有原始副本的情况下使其正常工作的任何建议?我可以重新打开 sys.stdout 吗?

【问题讨论】:

  • 您可以使用记录器代替打印。这是最佳做法。
  • 谢谢,我会调查的。很多有用的包,时间太少了……
  • @Brad 别再看了!输入 Python,这是第一种语言和标准库,其中 “应该有一个,并且最好只有 一个明显的方法来做到这一点”! 如果标准库中有一些功能,你可以打赌,他们很难让它涵盖每个典型用例。
  • (OT) 顺便说一句,那是毁灭公爵的参考吗? ;)

标签: python with-statement


【解决方案1】:

您可以在一个类中“包装”sys.stdout 以防止它首先被关闭。

with 语句在该类的开头和结尾分别调用 __enter____exit__,因此请确保 __exit__ 不执行任何操作:

class StdOut:
    def __enter__(self):
        return sys.stdout

    def __exit__(self, typ, val, trace):
        pass

stdout = StdOut()

然后使用stdout 而不是sys.stdout

【讨论】:

    【解决方案2】:

    字面问题 - 重新打开标准输出

    在最低的 C 级别,stdout 是众所周知的file descriptor(指向运行时或系统管理的描述符表中的条目的整数),在进程中初始化创建。 It cannot be reopened (with standard C means) once it's closed and must be duplicated beforehand if you still need it.

    sys.stdout 的一次性副本可以这样创建:

    stdout_copy=os.fdopen(os.dup(sys.stdout.fileno()))
    

    (在 Python 3 中,os.fdopen() 已合并为 open() 并且是它的别名。)

    如果sys.stdout 已被替换,您可能需要使用sys.__stdout__

    另一个问题 - 将功能包装到 with 逻辑中

    首先,考虑标准的日志方式——即logging 模块——以避免重新发明方轮。使用它的机器可以很好地实现按需打开和关闭文件,并且在绝大多数情况下甚至不需要。

    现在,削减代码重复部分的唯一方法是将重复部分包装到子例程中(或处理列表的代码块,其中包含描述每次迭代应该做什么的元素,但它只能是用过一次)。这里有三个概念部分,无论语法如何(它可以是try/finally,也可以是with):

    • 包装结构
      • 包括。异常处理
    • 开+关代码
    • 包装好的代码

    • 仅包装“开始+结束代码”是最简单的,the other answer 是一种可能的方式,但它会留下重复的withprint(........, file=fp) 部分。

    • 包装整个结构更加困难,因为您必须将代码块传递给您可能的子例程,并且 Python 有意省略匿名代码块 - 您必须 def 然后立即使用它尴尬。

      • 装饰器或将代码作为回调传递是两种可能性。
      • 如果您的代码可以简化为一种模式(如一组消息),您可以只传递该模式并让子例程处理它。

    【讨论】:

      猜你喜欢
      • 2014-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-01
      相关资源
      最近更新 更多