首页 > 解决方案 > 对变量的赋值正在更改对该变量先前值的所有其他引用

问题描述

我正在编写一个程序,对输出流进行一些创造性的重定向。我有一个我重定向的流的存储字典,与重定向它们的线程的 ID 相关联(我每个都有一个stdoutstderr- 后者在下面,_stderr_redirects)。稍后我将详细介绍重定向的工作原理,但只要说 dict 中的每个键对应于表示流列表的值就足够了。该列表中的最后一个就是要输出的那个。

为了避免代码重用,我尝试编写一种方法来添加到这个 dict ( _push_stream()) 中,可以使用指定流名称的字符串来调用它。这用于索引到_stream_redirects,它返回 dict _stderr_redirects

_stderr_redirects = {}
...
_stream_redirects = {"stderr":_stderr_redirects, ...}

def _push_stream(new_dest, stream_name):
    current_thread_id = threading.current_thread().ident
    if current_thread_id in _stream_redirects[stream_name]:
        _stream_redirects[stream_name][current_thread_id].append(new_dest)
    else:
        _stream_redirects[stream_name][current_thread_id] = [new_dest]  # DEBUG 1
    pass                                                                # DEBUG 2

我称这个方法如下:

with open("turn_off_stderr.txt", "r") as infile:
    _push_stream(infile, "stderr")

在我的代码中,我对自定义流的特定实例有三个不同的引用:在点 处DEBUG 1,它由变量sys.stderrsys.__stderr__和模块级变量指向_quiet_stderr

然而,只要我说到重点DEBUG 2,所有这三个引用都会更改为任何内容new_dest(在此示例中,文件描述符到文件turn_off_stderr.txt)。这种情况可靠且一致地发生,我有截图可以证明这一点。

为什么会发生这种情况,如何在遵守良好的代码重用标准的同时解决此问题?


我对此做了很多修改,经过几个小时的调试尝试,我发现了一个明显的切换,似乎与这种行为有因果关系。这是我要替换流的类(为清楚起见,注释内联/缩进):

_orig_sys_dests = {
    "stderr": sys.stderr,
    ...
}

class QuietIOWrapper(io.TextIOWrapper):
    def __init__(self, name):
        self._name = name
        super().__init__(_orig_sys_dests[name].buffer)

    def __getattribute__(self, item):
        if item in ["_name", "__setattr__"]:                                       # retrieve our own instance variables, using super() to avoid recursion error
            return super().__getattribute__(item)
        current_thread_id = threading.current_thread().ident                       # get the current thread's ID and the current self name reference
        stream_name = self._name
        try:                                                                       # get the current thread's stream, or if not set then the default stream
            # THE FOLLOWING LINE IS THE PROBLEMATIC ONE
            target_stream = _stream_redirects[stream_name][current_thread_id][-1]
        except (KeyError, IndexError):
            target_stream = _orig_sys_dests[stream_name]
        if target_stream == self:
            target_stream = _orig_sys_dests[stream_name]                           # avoid infinite recursion error by defaulting to user-intended stream if target is self
        return target_stream.__getattribute__(item)                                # forward request for attribute to the target stream


    def write(self, s):
        self.write(s)                                                              # Dummy method to redirect to default stream, if __getattribute__() would be bypassed

sys.stdout = QuietIOWrapper("stdout")
sys.stderr = QuietIOWrapper("stderr")
sys.__stdout__ = sys.stdout
sys.__stderr__ = sys.stderr

我的评论之后的那一行# THE FOLLOWING LINE IS PROBLEMATIC是决定此行为是否发生的切换。它目前的形式引发了这个问题。以下行也可以,只是现有的:

target_stream = _stderr_redirects[current_thread_id][-1]

如果省略了这一行(即_stderr_redirects这里没有提到 if,即使是通过代理),我上面描述的错误就不会发生。

我尝试在该行上放置一个断点,并且在发生上述错误之前它甚至没有被击中一次(我在另一个断点上捕获)。

那条线的存在一定会导致错误,但我完全不知道为什么,或者如何阻止它发生。这个错误与我目前对 python 作为一种语言的理解不兼容。

标签: pythonio

解决方案


推荐阅读