Python中模块变量__path__在这段代码中怎么传进来的?

看到一段很有趣的代码,实现了模块导入的时候,隐式的导入模块变量,比如:

from bluesprint_factory import bl

Python中模块变量__path__在这段代码中怎么传进来的?

在调试这段代码的时候,发现__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的代码做了什么事情:

  1. 定义了一个 Wrapper 类;
  2. 定义了一个 AutoBlueprint 类;
  3. bluesprint_factory 模块做了一次封装并替换。

 

再详细看上述第3步做了什么事:

sys.modules[__name__] = Wrapper(sys.modules[__name__])

  1. 先从 sys.modules 中得到代表当前py模块被解析后的module对象,先称之为origin_module
  2. origin_module对象传递给Wrapper类,得到一个wrapper 对象,称之为wrapped_module,该对象只具有一个属性self.wrapped,其值为origin_module ;
  3. 再把wrapped_module对象绑定给 sys.modules[__name__] 这个变量。

总之,经过最后一行代码的执行后:

  • sys.modules[__name__] = wrapped_module
  • wrapped_module.wrapped = orign_module

到此为止,bluesprint_factory.py 的初始化工作就完成了。

 

最后,再看从别的模块 from bluesprint_factory import bl会发生什么:

  1. 当执行 from x import y 的时候,Python 需要先判断 x 是否为package,而判断条件就是 x 是否具有 __path__属性 ,不论__path__的值如何。;
  2. 尝试访问 bluesprint_factory 模块对象——wrapped_module ——的__path__属性;
  3. 由于wrapped_module被实例化之后,并无__path__ 属性,进入 wrapped_module.__getattr__() ;此处就是你要的答案,为何 __getattr__ 拦截到了"__path__"因为 Python 的 import 机制去尝试访问了这个实例化后并不存在的属性
  4. wrapped_module.__getattr__() 中的 if 判断第一次显然会失败,然后走到 return getattr(self.wrapped, key) ,等价于 return getattr(orign_module, '__path__') ;
  5. 明显,上一步 return 之后的控制权限又回到了 Python 的 import 机制中,而且它得到None ,因为 orign_module 并非package,没有 '__path__'属性;
  6. 此时,import 机制知道了 from bluesprint_factory中的 bluesprint_factory就是一个正常的module,而非package,然后尝试从 bluesprint_factory 这个module 中获取bl属性;
  7. 接着需要拿到 bluesprint_factory的 module 对象,此时拿到的是 wrapped_module,因为你已经把sys.modules里的那个对象给替换了;
  8. 然后相当于要取得 wrapped_module.bl,同理,bl在初始化时并不存在,于是进入 wrapped_module.__getattr__()此时的key,已经成了'bl'
  9. 于是 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 。

 

上一篇:Codeforces Round #499 (Div. 1) F. Tree


下一篇:LOJ#2882. 「JOISC 2014 Day4」两个人的星座(计算几何)