python中萬物皆對象。維度比較大的有模塊、包。
一個.py文件就是一個python模塊(module),如果一個目錄下面有一個__init__.py
文件,那么這個目錄就是一個python包(package)。
當然,這只是極簡版的概念。實際上包是一種特殊的模塊,而任何定義了
__path__
屬性的模塊都被當做包。
以兩個下劃線開頭,以兩個下劃線結尾的屬性,暫稱魔法屬性(自創的),對應的有魔法方法。——用特別的格式表示能實現一些特別的功能。
在python代碼中使用模塊或包,需要使用import
語句。
有兩種import
方法:relative import和absolute import。
相對導入,是通過指出相對當前目錄位置的偏移來導入對應目錄下的模塊。
絕對導入,就是直接指出哪個包,哪個模塊。
當python解釋器看到import語句后,要做兩件事:找到module,將module加入到local namespace中。
具體就是,根據import的指示,去尋找(find_module
)到對應的module,并導入(load_module
)了。
在第一步查找時,遵循:
- 檢查
sys.modules
(保存了之前import的類庫的緩存),如果module被找到,則?到第二步。
- 檢查
sys.meta_path
。meta_path
是一個 list,?面保存著一些 finder 對象,如果找到該module的話,就會返回一個finder對象。 - 檢查?些隱式的finder對象,不同的python實現有不同的隱式finder,但是都會有
sys.path_hooks
,sys.path_importer_cache
以及sys.path
。 - 拋出
ImportError
。
詳細內容可以參考:
Python Import 機制與拓展
Python 的 import 機制
這里說幾個典型應用吧。
判斷模塊是否已導入
若已導入,必定在sys.modules
中。sys.modules
是已導入模塊的字典,key是模塊名,value是模塊對象。
獲取某已導入模塊的所有屬性
dir(sys.modules[module_name])
獲取對應key==module_name
的value
值。
附help(dir)
的輸出:
Help on built-in function dir in module __builtin__:
dir(...)
dir([object]) -> list of strings
If called without an argument, return the names in the current scope.
Else, return an alphabetized list of names comprising (some of) the attributes
of the given object, and of attributes reachable from it.
If the object supplies a method named __dir__, it will be used; otherwise
the default dir() logic is used and returns:
for a module object: the module's attributes.
for a class object: its attributes, and recursively the attributes
of its bases.
for any other object: its attributes, its class's attributes, and
recursively the attributes of its class's base classes.
在其中提到了,可以使用__dir__
魔法方法來自定義。
之后在inspect或是需要了解對象內部的情況時,還會用到它。
為什么會報ImportError,如何規定導入路徑
sys.path
是當前環境的module搜索路徑,如果想要找到某package,就需要將此package的目錄加入到這個list當中,也就是對應package中的__init__.py
文件所在的目錄。
如使用pip安裝的包就位于'/Library/Python/2.7/site-packages'目錄。
通常,在這個列表中,會加入當前目錄。
一個關于Class A和Class B遞歸導入的經典問題
見import 迷宮
sys.meta_path和其他path hook的區別
處理sys.path
時會使用sys.path_hooks
,它會順序地檢查sys.path
中的每一項,如不能處理則拋出ImportError,如果可以返回一個importer對象,并返回。
sys.meta_path
見下。
自定義導入,寫一個import hook
可以通過import hook,來在模塊導入時做一些工作。
我們已經知道,導入模塊首先是要進行查找。而查找的第一步是檢查sys.modules
,第二步是檢查sys.meta_path
,使用其中的finder對象來查找所需要的module。
正常情況下,sys.meta_path
是一個空列表。
finder對象必須實現find_module()
方法,它可以找到模塊,并返回一個load_module()
方法。
loader對象負責加載模塊,必須實現load_module()
方法。
importer對象,實現了find_module()
和load_module()
方法,即在實際中,只需要實現一個importer類,此類有find_module()
和load_module()
兩個方法。
具體可見上面引用的第一篇blog。
函數、類屬于哪個模塊
在一個文件中,可以通過from module_name import function_name
或者from module_name import class_name
,來導入其他模塊的函數,或類。
在這種情況下,會發現這些導入的對象,也存在于該模塊的dir輸出中。
可以通過這些對象的__module__
魔法屬性來判斷是在本模塊中定義,還是從外部導入。
使用這個方法,也可以得知某一方法是與類屬于同一模塊,還是繼承自基類(我并沒有去驗證基類和子類屬于同一模塊的情況,這種情況應該比較罕見吧?)。
相對導入和絕對導入
絕對導入就是明確指出是從哪個包或模塊導入。
相對導入是指相對于目前模塊所處的文件位置,來找到某個模塊來導入。如from . import xxx
是從當前模塊所在的文件的同層目錄中尋找,一個點表示同層目錄,兩個點表示上一層目錄,以此類推。
相對導入會有一些麻煩。推薦使用絕對導入,這也是python推薦的。通過設置sys.path
來將路徑加入,這樣在import時,python會到設置的路徑中自動匹配。
pkgutil
最常用的有兩個:
iter_modules(path=None, prefix='')
Yields (module_loader, name, ispkg) for all submodules on path, or, if path is None, all top-level modules on sys.path.
path
是包的目錄路徑,prefix是輸出時,所有包的名字的前綴。用來獲取該path下的子模塊或子包。
walk_packages(path=None, prefix='', onerror=None)
Yields (module_loader, name, ispkg) for all modules recursively on path, or, if path is None, all accessible modules.
同上,但是這個方法是遞歸獲取路徑下的所有模塊。
inspect
常同pkgutil結合用。
getmembers(object, predicate=None)
返回object的所有屬性,即dir中的各項。
這些屬性各種各樣,通過設置predicate
來進行篩選,如當predicate=ismethod
,只有當其為類的方法時,才會被選中。
getdoc(object)
獲取對象的docstring
,即__doc__
。
getargspec(func)
獲取函數對象的arg。
還有很多其他方法,詳見inspect的幫助文檔。
最后的TODO
最近在做一個自動化測試的項目,第一步就是要找到所有的模塊、類、方法、函數,以上就是涉及到的一部分基礎知識。
過程中,發現dir輸出的內容要遠遠多于真實在代碼里寫出來的東西,對于方法或屬性,也需要去了解一下。
Python 魔術方法指南可以起到一定的幫助作用,剩下的還需要繼續搜索。
其他相關博文:
python非侵入式代碼監控(一): python import hook
PEP 302 -- New Import Hooks
PEP 369 -- Post import hooks
Python源碼剖析筆記5-模塊機制