看到一段很有趣的代码,实现了模块导入的时候,隐式的导入模块变量,比如:
from bluesprint_factory import bl
在调试这段代码的时候,发现__getattr__方法首先进来的是__path__变量,这个变量怎么传进来的?并且getattr(self.wrapped, key="__path__")的时候,直接到key="bl"流程处理了,这很好玩啊。
有同学叫我帮忙协助剖析这段代码
# bluesprint_factory.py
import inspect
import sys
from flask import Blueprint
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, key):
if key == 'bl':
AutoBlueprint = self.wrapped.AutoBlueprint
return AutoBlueprint()
return getattr(self.wrapped, key)
class AutoBlueprint(object):
def __init__(self):
frm = sys._getframe(2)
module = inspect.getmodule(frm)
pack_name = module.__name__.rsplit('.', 1)[-1]
self.bl = Blueprint(mod.__name__, pack_name)
def __getattr__(self, key):
return getattr(self.bl, key)
sys.modules[__name__] = Wrapper(sys.modules[__name__])
作者:denglj
链接:https://www.zhihu.com/question/67447987/answer/253317374
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
先看bluesprint_factory.py
的代码做了什么事情:
- 定义了一个
Wrapper
类; - 定义了一个
AutoBlueprint
类; - 把
bluesprint_factory
模块做了一次封装并替换。
再详细看上述第3步做了什么事:
sys.modules[__name__] = Wrapper(sys.modules[__name__])
- 先从
sys.modules
中得到代表当前py模块被解析后的module
对象,先称之为origin_module
; - 将
origin_module
对象传递给Wrapper
类,得到一个wrapper
对象,称之为wrapped_module
,该对象只具有一个属性self.wrapped
,其值为origin_module
; - 再把
wrapped_module
对象绑定给sys.modules[__name__]
这个变量。
总之,经过最后一行代码的执行后:
sys.modules[__name__] = wrapped_module
wrapped_module.wrapped = orign_module
到此为止,bluesprint_factory.py
的初始化工作就完成了。
最后,再看从别的模块 from bluesprint_factory import bl
会发生什么:
- 当执行
from x import y
的时候,Python 需要先判断 x 是否为package,而判断条件就是 x 是否具有__path__
属性 ,不论__path__
的值如何。; - 尝试访问 bluesprint_factory 模块对象——
wrapped_module
——的__path__
属性; - 由于
wrapped_module
被实例化之后,并无__path__
属性,进入wrapped_module.__getattr__()
;此处就是你要的答案,为何__getattr__
拦截到了"__path__"
,因为 Python 的 import 机制去尝试访问了这个实例化后并不存在的属性; -
wrapped_module.__getattr__()
中的if
判断第一次显然会失败,然后走到return getattr(self.wrapped, key)
,等价于return getattr(orign_module, '__path__')
; - 明显,上一步
return
之后的控制权限又回到了 Python 的 import 机制中,而且它得到None
,因为orign_module
并非package,没有'__path__'
属性; - 此时,import 机制知道了
from bluesprint_factory
中的bluesprint_factory
就是一个正常的module,而非package,然后尝试从bluesprint_factory
这个module 中获取bl
属性; - 接着需要拿到
bluesprint_factory
的 module 对象,此时拿到的是wrapped_module
,因为你已经把sys.modules
里的那个对象给替换了; - 然后相当于要取得
wrapped_module.bl
,同理,bl
在初始化时并不存在,于是进入wrapped_module.__getattr__()
此时的key,已经成了'bl'
; - 于是
if key == 'bl'
条件成立,调用AutoBlueprint
类创建其实例。
后续AutoBlueprint
里的操作就不用再解释了吧。
经过上述解释后,应该可以知道,除了from bluesprint_factory import bl
可以触发上述流程来很trick地创建bl
对象,import bluesprint_factory; bl = bluesprint_factory.bl
同样可以。
这整个导入流程都是由 Python 的 import 机制控制,相关源代码参考cpython/Python/import.c 、cpython/Lib/importlib 。