Python __getattr__ 执行多次
Python __getattr__ executed multiple times
我一直在尝试实现 __getattr__
函数,如下例所示:
PEP 562 -- Module __getattr__
and __dir__
我不明白为什么要用这段简单的代码:
# lib.py
def __getattr__(name):
print(name)
# main.py
from lib import test
输出:
__path__
test
test
什么是__path__
?为什么发送到 __getattr__
?为什么 test
发送了 2 次?
TL;DR 打印的第一个“测试”是“从导入”实现的副作用,即它是在创建 lib
模块期间打印的。第二个“测试”来自后续直接访问模块上的动态属性。
知道 importlib
是在 Python 代码中实现的,稍微修改你的 lib.py
也转储一个痕迹:
# lib.py
from traceback import print_stack
def __getattr__(name):
print_stack()
print(name)
print("-" * 80)
这给出了精确定位 importlib 中触发双属性访问的库位置的提示:
$ python3 main.py
File "main.py", line 3, in <module>
from lib import test
File "<frozen importlib._bootstrap>", line 1019, in _handle_fromlist
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
__path__
--------------------------------------------------------------------------------
File "main.py", line 3, in <module>
from lib import test
File "<frozen importlib._bootstrap>", line 1032, in _handle_fromlist
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
test
--------------------------------------------------------------------------------
File "main.py", line 3, in <module>
from lib import test
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
test
--------------------------------------------------------------------------------
现在我们可以通过RTFS轻松找到答案(下面我用的是Pythonv3.7.6,如果版本不同,打开git到你使用的确切标签)。查看 importlib._bootstrap. _handle_fromlist
中指定的行号。
_handle_fromlist
是一个帮助程序,用于在 from
导入中加载包子模块。第 1 步是查看模块是否是一个包:
if hasattr(module, '__path__'):
__path__
访问位于第 1019 行。因为您的 __getattr__
returns None
对于所有输入,hasattr
returns True
在这里,所以你的模块看起来像一个包,代码继续。 (如果 hasattr
已 returned False
,_handle_fromlist
将在此时中止。)
此处的“fromlist”将包含您请求的名称,["test"]
,因此我们使用 x="test"
进入 for 循环,在第 1032 行有“额外”调用:
elif not hasattr(module, x):
如果 lib
还没有 test
属性,from lib import test
只会尝试加载 lib.test
子模块。此检查正在测试属性是否存在,以查看 _handle_fromlist
是否需要尝试加载子模块。
如果您 return 第一次和第二次调用名称为“test”的 __getattr__
的值不同,那么第二个值 returned 是实际收到的值在 main.py
.
内
我一直在尝试实现 __getattr__
函数,如下例所示:
PEP 562 -- Module __getattr__
and __dir__
我不明白为什么要用这段简单的代码:
# lib.py
def __getattr__(name):
print(name)
# main.py
from lib import test
输出:
__path__
test
test
什么是__path__
?为什么发送到 __getattr__
?为什么 test
发送了 2 次?
TL;DR 打印的第一个“测试”是“从导入”实现的副作用,即它是在创建 lib
模块期间打印的。第二个“测试”来自后续直接访问模块上的动态属性。
知道 importlib
是在 Python 代码中实现的,稍微修改你的 lib.py
也转储一个痕迹:
# lib.py
from traceback import print_stack
def __getattr__(name):
print_stack()
print(name)
print("-" * 80)
这给出了精确定位 importlib 中触发双属性访问的库位置的提示:
$ python3 main.py
File "main.py", line 3, in <module>
from lib import test
File "<frozen importlib._bootstrap>", line 1019, in _handle_fromlist
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
__path__
--------------------------------------------------------------------------------
File "main.py", line 3, in <module>
from lib import test
File "<frozen importlib._bootstrap>", line 1032, in _handle_fromlist
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
test
--------------------------------------------------------------------------------
File "main.py", line 3, in <module>
from lib import test
File "/private/tmp/lib.py", line 5, in __getattr__
print_stack()
test
--------------------------------------------------------------------------------
现在我们可以通过RTFS轻松找到答案(下面我用的是Pythonv3.7.6,如果版本不同,打开git到你使用的确切标签)。查看 importlib._bootstrap. _handle_fromlist
中指定的行号。
_handle_fromlist
是一个帮助程序,用于在 from
导入中加载包子模块。第 1 步是查看模块是否是一个包:
if hasattr(module, '__path__'):
__path__
访问位于第 1019 行。因为您的 __getattr__
returns None
对于所有输入,hasattr
returns True
在这里,所以你的模块看起来像一个包,代码继续。 (如果 hasattr
已 returned False
,_handle_fromlist
将在此时中止。)
此处的“fromlist”将包含您请求的名称,["test"]
,因此我们使用 x="test"
进入 for 循环,在第 1032 行有“额外”调用:
elif not hasattr(module, x):
如果 lib
还没有 test
属性,from lib import test
只会尝试加载 lib.test
子模块。此检查正在测试属性是否存在,以查看 _handle_fromlist
是否需要尝试加载子模块。
如果您 return 第一次和第二次调用名称为“test”的 __getattr__
的值不同,那么第二个值 returned 是实际收到的值在 main.py
.