我正遇到可怕的事情 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__
有人能就我如何解决这个问题提供一些见解吗?
1条答案
按热度按时间pgx2nnw81#
你几乎肯定想用
__getattr__
而不是__getattribute__
. 前者仅在普通示例变量查找失败时调用,但后者始终被调用。文档中对此进行了明确解释。这似乎也正是你想要的,除非我遗漏了什么。例如,您希望能够以正常方式访问某些变量thread
及update
,但您希望在其他情况下提供特殊功能。那正是我想要的__getattr__
这是给你的。这是很难实施的
__getattribute__
正确,因为您必须通过显式调用__getattr__
在这些情况下。我看不出你为什么要这么做。我的建议是直接替换
__getattribute__
通过__getattr__
并尝试再次运行您的程序。