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

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

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

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

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

现行代码

  1. import threading
  2. class ThreadManager(object):
  3. """
  4. A custom thread-aware class for managing multiple sets of variables
  5. without the need to keep track of the current, working thread
  6. """
  7. def __init__(self, *args,**kwargs):
  8. assert threading.current_thread() is threading.main_thread(), \
  9. "ThreadManager must be instantiated from MainThread"
  10. self._threads = dict()
  11. self._thread_vars = dict(**dict.fromkeys(args),**kwargs)
  12. @property
  13. def thread(self) -> dict:
  14. ident = self.thread_id
  15. if ident not in self._threads:
  16. # new thread, create new key/value pair with default thread_vars dict as value
  17. self._threads[ident] = self._thread_vars
  18. return self._threads[ident]
  19. @property
  20. def thread_id(self) -> int:
  21. return threading.current_thread().ident
  22. def update(self, data: dict) -> None:
  23. ident = self.thread_id
  24. for var, value in data.items():
  25. if var not in self._thread_vars:
  26. raise AttributeError("%r was not found in the list of original thread variables" % var)
  27. self._threads[ident][var] = value
  28. def clear(self) -> None:
  29. """Clears a single thread's variables and re-instates the default values"""
  30. ident = self.thread_id
  31. self._threads[ident].clear()
  32. self._threads[ident].update(self._thread_vars)
  33. # region dunders
  34. def __setattr__(self, var, value):
  35. """
  36. >>> tm = ThreadManager("a", "b", c=False)
  37. >>> tm.b
  38. >>> tm.b = "data"
  39. >>> tm.b
  40. "data"
  41. """
  42. print(f"__setattr__: var={var}, value={value}")
  43. _ = self.thread # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
  44. self._threads[self.thread_id][var] = value
  45. def __getattr__(self, var):
  46. """
  47. >>> tm = ThreadManager("a", "b", c=False)
  48. >>> tm.b
  49. >>> tm.c
  50. False
  51. """
  52. print(f"__getattr__: var={var}")
  53. return self.thread[var]
  54. def __setitem__(self, var, value):
  55. """
  56. >>> tm = ThreadManager("a", "b", c=False)
  57. >>> tm["b"]
  58. >>> tm["b"] = "data"
  59. >>> tm["b"]
  60. "data"
  61. """
  62. print(f"__setitem__: var={var}, value={value}")
  63. _ = self.thread # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
  64. self._threads[self.thread_id][var] = value
  65. def __getitem__(self, var):
  66. """
  67. >>> tm = ThreadManager("a", "b", c=False)
  68. >>> tm["b"]
  69. >>> tm["c"]
  70. False
  71. """
  72. print(f"__getitem__: var={var}")
  73. return self.thread[var]
  74. def __len__(self):
  75. """Total number of threads being managed"""
  76. return len(self._threads)
  77. # endregion
  78. if __name__ == "__main__":
  79. tm = ThreadManager("a", "b", c={"d": 1})

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

  1. __setattr__: var=_threads, value={}
  2. __getattr__: var=_threads
  3. __getattr__: var=_threads
  4. __getattr__: var=_threads
  5. ...
  6. __getattr__: var=_threads
  7. __getattr__: var=_threads
  8. Traceback (most recent call last):
  9. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 1266, in <module>
  10. tm = ThreadManager("a", "b", c={"d": 1})
  11. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 87, in __init__
  12. self._threads = dict()
  13. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 125, in __setattr__
  14. _ = self.thread # FIXME: Hacky way to ensure the thread and accompanying dict exist before updating it
  15. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
  16. if ident not in self._threads:
  17. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
  18. return self.thread[var]
  19. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
  20. if ident not in self._threads:
  21. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
  22. return self.thread[var]
  23. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 93, in thread
  24. if ident not in self._threads:
  25. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 136, in __getattr__
  26. return self.thread[var]
  27. ...
  28. File "C:/Users/mhill/PycharmProjects/reporting/app/reports/base.py", line 100, in thread_id
  29. return threading.current_thread().ident
  30. File "C:\Users\mhill\AppData\Local\Programs\Python\Python37\lib\threading.py", line 1233, in current_thread
  31. return _active[get_ident()]
  32. 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__ 并尝试再次运行您的程序。

相关问题