在python类中尝试实现uuu getattribute_uuu/uuu setattr_uuu和u getitem_uuuuu/uuu setitem_uuu时出现递归错误

bejyjqdl  于 2021-09-08  发布在  Java
关注(0)|答案(1)|浏览(530)

我正遇到可怕的事情 RecursionError 在尝试实现一个本质上充当私有变量之间的代理的类时 _threads . 其思想是让类充当线程感知字典,为类示例的每次调用提供分配给特定线程的正确字典。

背景(如果你不在乎,可以跳过)

我发现自己需要这样做,同时并行化一些报告代码,这些代码跨越最初访问许多(一致)示例变量的许多函数。该类基本上是一种避免在每个函数中获取当前线程标识并在示例字典中手动执行查找的方法。另外,对于在其他项目中的可重用性,我想为什么不呢!

现行代码

import threading

class ThreadManager(object):
    """
    A custom thread-aware class for managing multiple sets of variables
    without the need to keep track of the current, working thread
    """

    def __init__(self, *args,**kwargs):
        assert threading.current_thread() is threading.main_thread(), \
            "ThreadManager must be instantiated from MainThread"
        self._threads = dict()
        self._thread_vars = dict(**dict.fromkeys(args),**kwargs)

    @property
    def thread(self) -> dict:
        ident = self.thread_id
        if ident not in self._threads:
            # new thread, create new key/value pair with default thread_vars dict as value
            self._threads[ident] = self._thread_vars
        return self._threads[ident]

    @property
    def thread_id(self) -> int:
        return threading.current_thread().ident

    def update(self, data: dict) -> None:
        ident = self.thread_id
        for var, value in data.items():
            if var not in self._thread_vars:
                raise AttributeError("%r was not found in the list of original thread variables" % var)
            self._threads[ident][var] = value

    def clear(self) -> None:
        """Clears a single thread's variables and re-instates the default values"""
        ident = self.thread_id
        self._threads[ident].clear()
        self._threads[ident].update(self._thread_vars)

    # region dunders
    def __setattr__(self, var, value):
        """
        >>> tm = ThreadManager("a", "b", c=False)
        >>> tm.b
        >>> tm.b = "data"
        >>> tm.b
        "data"
        """
        print(f"__setattr__: var={var}, value={value}")
        _ = self.thread  # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
        self._threads[self.thread_id][var] = value

    def __getattr__(self, var):
        """
        >>> tm = ThreadManager("a", "b", c=False)
        >>> tm.b
        >>> tm.c
        False
        """
        print(f"__getattr__: var={var}")
        return self.thread[var]

    def __setitem__(self, var, value):
        """
        >>> tm = ThreadManager("a", "b", c=False)
        >>> tm["b"]
        >>> tm["b"] = "data"
        >>> tm["b"]
        "data"
        """
        print(f"__setitem__: var={var}, value={value}")
        _ = self.thread  # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
        self._threads[self.thread_id][var] = value

    def __getitem__(self, var):
        """
        >>> tm = ThreadManager("a", "b", c=False)
        >>> tm["b"]
        >>> tm["c"]
        False
        """
        print(f"__getitem__: var={var}")
        return self.thread[var]

    def __len__(self):
        """Total number of threads being managed"""
        return len(self._threads)
    # endregion

if __name__ == "__main__":
    tm = ThreadManager("a", "b", c={"d": 1})

使用上面的代码,我最终得到 RecursionError 和回溯:

__setattr__: var=_threads, value={}
__getattr__: var=_threads
__getattr__: var=_threads
__getattr__: var=_threads
...
__getattr__: var=_threads
__getattr__: var=_threads
Traceback (most recent call last):
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 1266, in <module>
    tm = ThreadManager("a", "b", c={"d": 1})
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 87, in __init__
    self._threads = dict()
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 125, in __setattr__
    _ = self.thread  # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
    if ident not in self._threads:
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
    return self.thread[var]
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
    if ident not in self._threads:
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
    return self.thread[var]
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
    if ident not in self._threads:
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
    return self.thread[var]
...
  File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 100, in thread_id
    return threading.current_thread().ident
  File "C:\Users\mhill\AppData\Local\Programs\Python\Python37\lib\threading.py", line 1233, in current_thread
    return _active[get_ident()]
RecursionError: maximum recursion depth exceeded while calling a Python object

我试图保持将字典项作为属性访问的能力(使用 tm.property_name )和您将使用的典型字典项一样(使用 tm["property_name"] ),这就是我试图实现这两个目标的原因 __getattribute__/__setattr____getitem__/__setitem__ 有人能就我如何解决这个问题提供一些见解吗?

pgx2nnw8

pgx2nnw81#

你几乎肯定想用 __getattr__ 而不是 __getattribute__ . 前者仅在普通示例变量查找失败时调用,但后者始终被调用。文档中对此进行了明确解释。这似乎也正是你想要的,除非我遗漏了什么。例如,您希望能够以正常方式访问某些变量 threadupdate ,但您希望在其他情况下提供特殊功能。那正是我想要的 __getattr__ 这是给你的。
这是很难实施的 __getattribute__ 正确,因为您必须通过显式调用 __getattr__ 在这些情况下。我看不出你为什么要这么做。
我的建议是直接替换 __getattribute__ 通过 __getattr__ 并尝试再次运行您的程序。

相关问题