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 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__])


大概是需要判斷一下你from的這個變數究竟是個package還是個module吧,如果是個package,邏輯和module的情況略有不同。判斷的方法似乎用到了__path__。具體可以查一下相關的源代碼。


既然小權權都問了,正好今下午有點時間,那就答一下吧。:-D

先看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 。

-------------------分割線---------------

請問題主我可否將這個問題和我的答案整理後轉發到我的微信公眾號:jushuoms(駒說碼事)?歡迎 Pythonor 關注我的公眾號。


推薦閱讀:

Python socket 遇到錯誤 [Errno 10060] ?
Flask框架怎麼樣,比起Web.py有哪些不同?
Tornado 非同步讀寫文件的方法?
學完python之後去看《flask web開發-基於python的web應用開發實戰》為什麼看不懂?
用Python寫爬蟲,用什麼方式、框架比較好?

TAG:Python | 編程 | Flask | Python框架 | Python編程 |