在模塊載入的時候為其打補丁

這個問題的關鍵在於我們想針對正在載入的模塊執行響應操作。當某個模塊得到載入的時候,也許我們想觸發某種形式的回調函數來通知這一事實。下面是一種可能的解決方法,用上一節講的鉤子來進行實現

import importlib
import sys
from collections import defaultdict

_post_import_hooks = defaultdict(list)

class PostImportFinder:
def __init__(self):
self._skip = set()

def find_module(self, fullname, path=None):
if fullname in self._skip:
return None
self._skip.add(fullname)
return PostImportLoader(self)

class PostImportLoader:
def __init__(self, finder):
self._finder = finder

def load_module(self, fullname):
importlib.import_module(fullname)
module = sys.modules[fullname]
for func in _post_import_hooks[fullname]:
func(module)
self._finder._skip.remove(fullname)
return module

def when_imported(fullname):
def decorate(func):
if fullname in sys.modules:
func(sys.modules[fullname])
else:
_post_import_hooks[fullname].append(func)
return func

return decorate

sys.meta_path.insert(0, PostImportFinder())

from functools import wraps

def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(Calling, func.__name__, args, kwargs)
return func(*args, **kwargs)

return wrapper

# Example
@when_imported(math)
def add_logging(mod):
mod.cos = logged(mod.cos)
mod.sin = logged(mod.sin)

本節技術依賴於10.11小節中講述過的導入鉤子,並稍作修改。@when_imported 裝飾器的作用是註冊在導入時被激活的處理器函數。 該裝飾器檢查sys.modules來查看模塊是否真的已經被載入了。 如果是的話,該處理器被立即調用。不然,處理器被添加_post_import_hooks 字典中的一個列表中去。 _post_import_hooks 的作用就是收集所有的為每個模塊註冊的處理器對象。 一個模塊可以註冊多個處理器。

要讓模塊導入後觸發添加的動作,PostImportFinder 類被設置為sys.meta_path第一個元素。 它會捕獲所有模塊導入操作。

本節中的 PostImportFinder 的作用並不是載入模塊,而是自帶導入完成後觸發相應的動作。 實際的導入被委派給位於sys.meta_path中的其他查找器。 PostImportLoader 類中的 imp.import_module() 函數被遞歸的調用。 為了避免陷入無線循環,PostImportFinder 保持了一個所有被載入過的模塊集合。 如果一個模塊名存在就會直接被忽略掉。當一個模塊被 imp.import_module() 載入後, 所有在_post_import_hooks被註冊的處理器被調用,使用新載入模塊作為一個參數。

有一點需要注意的是本機不適用於那些通過 imp.reload() 被顯式載入的模塊。 也就是說,如果你載入一個之前已被載入過的模塊,那麼導入處理器將不會再被觸發。 另外,要是你從sys.modules中刪除模塊然後再重新導入,處理器又會再一次觸發。

安裝私有包

Python有一個用戶安裝目錄,通常類似」~/.local/lib/python3.3/site-packages」。 要強制在這個目錄中安裝包,可使用安裝選項「–user」。例如:

python3 setup.py install --user

在sys.path中用戶的「site-packages」目錄位於系統的「site-packages」目錄之前。 因此,你安裝在裡面的包就比系統已安裝的包優先順序高 (儘管並不總是這樣,要取決於第三方包管理器,比如distribute或pip)。

通常包會被安裝到系統的site-packages目錄中去,路徑類似「/usr/local/lib/python3.3/site-packages」。 不過,這樣做需要有管理員許可權並且使用sudo命令。 就算你有這樣的許可權去執行命令,使用sudo去安裝一個新的,可能沒有被驗證過的包有時候也不安全。

安裝包到用戶目錄中通常是一個有效的方案,它允許你創建一個自定義安裝。另外,你還可以創建一個虛擬環境,這個我們在下一節會講到。

創建一個新的Python虛擬環境

對於一般情況來說,我們可以使用pyvenv命令創建一個新的「虛擬」環境。 這個命令被安裝在Python解釋器同一目錄,或Windows上面的Scripts目錄中。下面是一個例子:

bash % pyvenv Spam
bash %

傳給pyvenv命令的名字是將要被創建的目錄名。當被創建後,Span目錄像下面這樣:

bash % cd Spam
bash % ls
bin include lib pyvenv.cfg
bash %

在bin目錄中,你會找到一個可以使用的Python解釋器:

bash % Spam/bin/python3
Python 3.3.0 (default, Oct 6 2012, 15:45:22)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pprint import pprint
>>> import sys
>>> pprint(sys.path)
[,
/usr/local/lib/python33.zip,
/usr/local/lib/python3.3,
/usr/local/lib/python3.3/plat-darwin,
/usr/local/lib/python3.3/lib-dynload,
/Users/beazley/Spam/lib/python3.3/site-packages]
>>>

這個解釋器的特點就是他的site-packages目錄被設置為新創建的環境。 如果你要安裝第三方包,它們會被安裝在那裡,而不是通常系統的site-packages目錄。創建虛擬環境通常是為了安裝和管理第三方包。 正如你在例子中看到的那樣,sys.path 變數包含來自於系統Python的目錄, 而 site-packages目錄已經被重定位到一個新的目錄。

有了一個新的虛擬環境,下一步就是安裝一個包管理器,比如distribute或pip。 但安裝這樣的工具和包的時候,你需要確保你使用的是虛擬環境的解釋器。 它會將包安裝到新創建的site-packages目錄中去。

儘管一個虛擬環境看上去是Python安裝的一個複製, 不過它實際上只包含了少量幾個文件和一些符號鏈接。 所有標準庫函文件和可執行解釋器都來自原來的Python安裝。 因此,創建這樣的環境是很容易的,並且幾乎不會消耗機器資源。

默認情況下,虛擬環境是空的,不包含任何額外的第三方庫。如果你想將一個已經安裝的包作為虛擬環境的一部分, 可以使用「–system-site-packages」選項來創建虛擬環境,

參考書目

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Github地址:

yidao620c/python3-cookbook?

github.com
圖標

推薦閱讀:
相关文章