为什么我不能使用“from sys import stdout”重定向标准输出?

3
我将尝试将Python脚本的标准输出重定向到文件中。
如果使用sys模块导入STDOUT,脚本的输出将不会被重定向到文件中。请参考以下代码:
from sys import stdout
stdout = open("text", "w")
print("Hello")

然而,如果我仅导入sys并使用sys.stdout,则脚本的输出会成功重定向:
import sys
sys.stdout = open("text", "w")
print("Hello")

为什么会这样呢?根据这个答案,"import X" 和 "from X import Y" 的唯一区别是绑定的名称。那么这个区别如何影响stdout呢?


我认为第一种情况是你正在重新定义整个模块。而第二种情况是你正在重新分配sys模块内的引用。 - OneCricketeer
@cricket_007 不,他并没有重新定义整个模块。 - abarnert
@abarnert 我的意思是局部变量。 - OneCricketeer
4个回答

4

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

无论哪种方式,将Y绑定到其他内容都不会影响X中的任何内容。


如果这样更容易理解,请考虑以下类比:

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

您觉得这会将 y 改为 3 吗?当然不会。但实际上你正在做完全相同的事情。


如果还不清楚,让我们分解一下那些 import 到底做了什么。

当您执行 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")

1
啊,那么 sys.stdout 的值本质上会被复制到同名的局部变量中? - Cat
1
@Cat 没错,是全局的而不是局部的,但很接近。并且,像那样谈论“复制”有点令人困惑。在内存中有一个值——一个文件对象,还有一个变量,stdout,位于sys模块的全局变量中,指向该值。这个值永远不会被复制,但从某种意义上说,对该值的引用确实会被复制到您的模块的全局命名空间中的名称“stdout”中。人们使用术语“Y绑定到X”之类的术语来避免混淆——但当然,直到您学会了这个行话,它只会让事情变得更加混乱... - abarnert
@Cat 如果你来自像C这样的语言,那么变量具有内存位置、类型和标识符,而值只是变量中的位模式。Python不是这样的。Python 具有内存位置、类型和标识符,它的变量只是存储在命名空间(如模块或类实例)中的值的名称。 (命名空间基本上只是一个字典,如果您不想直接查看它,则不必这样做。) - abarnert
我明白了,我想我现在懂了。感谢你的有益回答! - Cat

1

它与以下内容相同:

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

在这种情况下,您并没有更改some_object.some_attr。您只是分配给一个本地值。
当您使用sys.stdout = ...时,实际上正在更新stdout。

1
我这么做的方法是创建一个上下文管理器。
@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    

0

通过这个小技巧,你可以节省一些精力并赚取一些“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'] = ...

希望这能增加一点清晰度!


1
这不是完全正确的——应该是globals()['sys'].__dict__['STDOUT']。当然,如果模块只是字典而不是具有字典的对象,那么相关效果仍然相同,因此解释仍然有效,但如果它与在交互式解释器中玩耍时所看到的完全匹配,而不是引发AttributeError,那么它将是一个更好的说明。 - abarnert
1
此外,我知道OP在问题的文本中使用了STDOUT,但更好的做法是纠正并使用实际名称stdout,而不是演示不存在的名称STDOUT - abarnert
好的 :) 我会进行相关更改。你知道,以防有人在这里跌跤并随意选择一个答案。 - snakes_on_a_keyboard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接