Python中模塊變數__path__在這段代碼中怎麼傳進來的?
看到一段很有趣的代碼,實現了模塊導入的時候,隱式的導入模塊變數,比如:
from bluesprint_factory import bl
在調試這段代碼的時候,發現__getattr__方法首先進來的是__path__變數,這個變數怎麼傳進來的?並且getattr(self.wrapped, key="__path__")的時候,直接到key="bl"流程處理了,這很好玩啊,求大拿協助剖析這段代碼。
# bluesprint_factory.py
import inspect
import sys
from flask import Blueprintclass Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrappeddef __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__])
大概是需要判斷一下你from的這個變數究竟是個package還是個module吧,如果是個package,邏輯和module的情況略有不同。判斷的方法似乎用到了__path__。具體可以查一下相關的源代碼。
既然小權權都問了,正好今下午有點時間,那就答一下吧。:-D
先看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 。
-------------------分割線---------------
請問題主我可否將這個問題和我的答案整理後轉發到我的微信公眾號:jushuoms(駒說碼事)?歡迎 Pythonor 關注我的公眾號。
推薦閱讀:
※Python socket 遇到錯誤 [Errno 10060] ?
※Flask框架怎麼樣,比起Web.py有哪些不同?
※Tornado 非同步讀寫文件的方法?
※學完python之後去看《flask web開發-基於python的web應用開發實戰》為什麼看不懂?
※用Python寫爬蟲,用什麼方式、框架比較好?