【问题标题】:Why can I not redirect STDOUT using "from sys import stdout"?为什么我不能使用“from sys import stdout”重定向 STDOUT?
【发布时间】:2018-09-22 00:39:57
【问题描述】:

我正在尝试将 python 脚本的 STDOUT 重定向到文件。

如果 STDOUT 是 sys 导入的,则脚本的输出不会不会被重定向到文件:

from sys import stdout
stdout = open("text", "w")
print("Hello")

但是,如果我导入 only sys 并使用 sys.stdout,脚本的输出会成功重定向:

import sys
sys.stdout = open("text", "w")
print("Hello")

这是为什么?根据this answer,“import X”和“from X import Y”之间的唯一区别是绑定的名称。这如何影响标准输出?

【问题讨论】:

  • 我认为首先,您正在重新定义整个模块。第二,您正在重新分配 sys 模块中的引用
  • @cricket_007 不,他没有重新定义整个模块。
  • @abarnert 我的意思是局部变量

标签: python stdout python-import sys


【解决方案1】:

是的,唯一的区别是名称Y 绑定到X.Y

无论如何,将Y 绑定到其他东西不会影响X 中的任何内容。


如果它更容易,考虑这个并行:

>>> y = 2
>>> x = y
>>> x = 3

您是否希望这会将y 更改为3?当然不是。但这与您正在做的事情完全相同。


如果还不清楚,让我们分解一下imports 的实际操作。

当你import sys时,就相当于:

sys.modules['sys'] = __import__('sys')
sys = sys.modules['sys']
sys.stdout = open(text, "w")

但是用from sys import stdout:

sys.modules['sys'] = __import__('sys')
stdout = sys.modules['sys'].stdout
stdout = open(text, "w")

【讨论】:

  • 啊,那么 sys.stdout 的值本质上是复制到同名的局部变量吗?
  • @Cat 没错。好吧,全球性的,不是本地的,但足够接近。而且,像这样谈论“复制”有点令人困惑。有一个值——内存中某处的文件对象,在sys 模块的全局变量中有一个变量stdout 引用该值。该值永远不会被复制,但从某种意义上说,对该值的引用确实会复制到模块全局命名空间中的名称 stdout 中。人们使用诸如Y is bound to X 之类的术语来避免混淆——但当然,在你了解这些术语之前,它只会让事情变得更加混乱……
  • @Cat 如果您来自像 C 这样的语言,其中变量具有内存位置、类型和标识,而值只是变量中的位模式。 Python不是那样的。 Python 具有内存位置、类型和标识,其变量只是存储在命名空间(如模块或类实例)中的值的名称。 (并且命名空间基本上只是一个字典,如果您不想看到,则不必直接查看。)
  • 我明白了,我想我现在明白了。感谢您的帮助!
【解决方案2】:

同理:

x = some_object.some_attr
x = open(...)

在这种情况下,您不会更改 some_object.some_attr。您只是分配给本地值。

当您使用sys.stdout = ... 时,您实际上是在更新标准输出。

【讨论】:

    【解决方案3】:

    我这样做的方式是创建一个上下文管理器。

    @contextmanager
    def suppress_stdout():
        with open(os.devnull, 'w') as devnull:
            old_stdout = sys.stdout
            old_stderr = sys.stderr
            sys.stdout = devnull
            try:
                yield
            finally:
                sys.stdout = old_stdout
                sys.stderr = old_stderr
    

    然后当我想抑制某个命令的标准输出时:

    with suppress_stdout():
        # suppressed commands    
    

    【讨论】:

      【解决方案4】:

      通过这个小技巧,您可以节省一些精力并为自己赢得一些“Pythonic”积分:

      import sys
      print('hello', file=sys.stdout)
      

      当然,print 默认情况下已经转到sys.stdout,所以也许我遗漏了一些东西。我不确定open('text', 'w') 是怎么回事,但如果你这样做可能没有必要:)

      在回答您关于变量分配影响的问题时,当您对变量使用 = 运算符时,实际上是将其分配给范围字典中的值(在本例中为 globals)。

      因此,当您导入 sys 时,sys 会被导入到 globals 字典中。

      所以globals 看起来像,

      {...,
      'sys': <module 'sys' (built-in)>}
      

      您可以将模块本身视为字典。所以当你做sys.stdout= ...就像在做globals()['sys'].__dict__['stdout'] =...

      当你只导入stdout 时,globals 看起来像:

      {...,
      'stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>}
      

      因此,当您执行 stdout=... 时,您实际上是在直接替换字典中的那个键:

      globals()['stdout'] = ...

      希望这有助于增加一点清晰度!

      【讨论】:

      • 这不太对——它是globals()['sys'].__dict__['STDOUT']。当然,如果模块只是 dicts 而不是具有 dicts 的对象,则相关效果将是相同的,因此解释仍然有效,但如果它与您在交互式解释器中玩耍时看到的完全匹配,那将是一个更好的说明,而不是提出AttributeError..
      • 另外,我知道 OP 在他的问题文本中使用了STDOUT,但最好更正它并使用实际名称stdout,而不是使用不存在的名称进行演示STDOUT.
      • 同意 :) 我会进行相关更改。你知道,万一有些人在这里偶然发现并随机选择一个答案。
      猜你喜欢
      • 1970-01-01
      • 2019-10-28
      • 2018-04-03
      • 2023-03-22
      • 1970-01-01
      • 2021-11-30
      • 1970-01-01
      • 2018-12-09
      • 1970-01-01
      相关资源
      最近更新 更多