Python __getattr__ executed multiple times
Python __getattr__ executed multiple times
我一直在尝试实现如下示例中的__getattr__
函数:\nPEP 562 -- 模块__getattr__
和__dir__
\n但是我不明白为什么这段简单的代码会输出:\n
# lib.py def __getattr__(name): print(name) # main.py from lib import test
\n输出结果是:\n
__path__ test test
\n__path__
是什么?为什么它会被发送到__getattr__
中?为什么test
会被发送两次?
Python __getattr__ executed multiple times的问题出现的原因是,在进行from import时,导入的模块会被创建,而创建过程中会执行__getattr__方法。解决方法是,在lib.py文件中进行修改,在__getattr__方法中添加一个trace,以便定位触发多次属性访问的库位置。
具体的解决方法如下:
在lib.py文件中添加以下代码:
from traceback import print_stack def __getattr__(name): print_stack() print(name) print("-" * 80)
运行main.py文件后,会得到以下输出:
File "main.py", line 3, infrom lib import test File " ", line 1019, in _handle_fromlist File "/private/tmp/lib.py", line 5, in __getattr__ print_stack() __path__ -------------------------------------------------------------------------------- File "main.py", line 3, in from lib import test File " ", line 1032, in _handle_fromlist File "/private/tmp/lib.py", line 5, in __getattr__ print_stack() test -------------------------------------------------------------------------------- File "main.py", line 3, in from lib import test File "/private/tmp/lib.py", line 5, in __getattr__ print_stack() test --------------------------------------------------------------------------------
通过上述输出可以得到线索,可以通过RTFS(Read The Fucking Source)在importlib库中找到问题的答案。在importlib._bootstrap._handle_fromlist的代码中,我们可以看到以下内容:
if hasattr(module, '__path__'):
在这里,我们可以看到对__path__属性的访问,这是问题出现的地方。因为__getattr__方法对所有的输入返回None,所以这里的hasattr返回了True,所以模块看起来像是一个包,代码会继续进行。(如果hasattr返回了False,_handle_fromlist将会在此处中止。)
在这里,"fromlist"将会是你请求的名称,即["test"],所以我们进入了for循环,x="test",在1032行处有一个额外的调用:
elif not hasattr(module, x):
只有当lib没有名为test的属性时,from lib import test才会尝试加载lib.test子模块。这个检查是为了测试属性是否存在,以确定_handle_fromlist是否需要尝试加载子模块。
如果你在name为"test"的第一次和第二次调用__getattr__方法时返回不同的值,那么实际上会接收到第二次返回的值。
通过以上分析,我们可以得出结论,Python中的__getattr__方法在进行from import时会被执行多次的问题是由于importlib库在创建模块时触发了多次属性访问的副作用导致的。要解决这个问题,可以在__getattr__方法中添加适当的判断,以避免重复执行多次。