簡(jiǎn)單談?wù)凱ython中的模塊導(dǎo)入
本文不討論 Python 的導(dǎo)入機(jī)制(底層實(shí)現(xiàn)細(xì)節(jié)),僅討論模塊與包,以及導(dǎo)入語句相關(guān)的概念。通常,導(dǎo)入模塊都是使用如下語句:
import ... import ... as ... from ... import ... from ... import ... as ...
一般情況下,使用以上語句導(dǎo)入模塊已經(jīng)夠用的。但是在一些特殊場(chǎng)景中,可能還需要其他的導(dǎo)入方式。例如 Python 還提供了 __import__ 內(nèi)建函數(shù)和 importlib 模塊來實(shí)現(xiàn)動(dòng)態(tài)導(dǎo)入。動(dòng)態(tài)導(dǎo)入的好處是可以延遲模塊的加載,僅在用到模塊時(shí)才支持導(dǎo)入動(dòng)作。
運(yùn)用 __import__ 函數(shù)和 importlib 模塊固然能夠?qū)崿F(xiàn)模塊的延遲加載,但其不足之處是,在任何需要用到指定模塊的地方都要實(shí)現(xiàn)一遍同樣的導(dǎo)入語句,這樣是不便于維護(hù)且非常麻煩的。如果能夠在頂層實(shí)現(xiàn)惰性導(dǎo)入,則是一個(gè)更好的選擇,這也是本文最終要討論的點(diǎn)。
在討論一些高級(jí)用法之前,首先需要了解一下模塊與包的概念。
模塊與包
模塊 可以理解為是 Python 可以加載并執(zhí)行的代碼文件,代碼文件不僅可以是 .py 文件,還可以是 .so 等其他類型的文件。Python 只有一個(gè) 模塊 對(duì)象型態(tài),而且所有模塊都是這個(gè)型態(tài)。為了便于組織多個(gè)模塊并提供一個(gè)模塊層次結(jié)構(gòu)的命名,Python 提供了 包 的概念。
可以簡(jiǎn)單的將包看作是一個(gè)文件系統(tǒng)的目錄,將模塊看作是目錄中的代碼文件(注意,不能完全地這樣認(rèn)為,因?yàn)榘湍K并非僅來自文件系統(tǒng),還可以來自壓縮文件、網(wǎng)絡(luò)等)。類似于文件系統(tǒng)的目錄結(jié)構(gòu),包被分級(jí)組織起來,而且包本身也可以包含子包和常規(guī)模塊。
包其實(shí)可以看作是一種特殊的模塊。例如常規(guī)包(下面會(huì)介紹常規(guī)包的概念)的目錄中需要包含 __init__.py 文件,當(dāng)包被導(dǎo)入時(shí),該文件的頂層代碼被隱式執(zhí)行,就如同模塊導(dǎo)入時(shí)頂層代碼被執(zhí)行,該文件就像是包的代碼一樣。所以 包是一種特殊的模塊。需要記住的是,所有的包都是模塊,但不是所有的模塊都是包。包中子包和模塊都有 __path__ 屬性,具體地說,任何包含 __path__ 屬性的模塊被認(rèn)為是包。所有的模塊都有一個(gè)名稱,類似于標(biāo)準(zhǔn)屬性訪問語法,子包與他們父包的名字之間用點(diǎn)隔開。
Python 定義了兩種類型的包,即 常規(guī)包 和 命名空間包。常規(guī)包是存在于 Python 3.2 及更早版本中的傳統(tǒng)包。常規(guī)包即包含 __init__.py 文件的目錄。當(dāng)導(dǎo)入一個(gè)常規(guī)包時(shí),__init__.py 文件被隱式執(zhí)行,而且它定義的對(duì)象被綁定到包命名空間中的名稱。 __init__.py 文件能包含其他任何模塊能夠包含的相同的 Python 代碼,而且在導(dǎo)入時(shí),Python 將給模塊增加一些額外的屬性。
從 Python 3.3 開始,Python 引入了 命名空間包 的概念。命名空間包是不同文件集的復(fù)合,每個(gè)文件集給父包貢獻(xiàn)一個(gè)子包,所有的包中都不需要包含 __init__.py 文件。文件集可以存于文件系統(tǒng)的不同位置。文件集的查找包含導(dǎo)入過程中 Python 搜索的壓縮文件,網(wǎng)絡(luò)或者其他地方。命名空間包可以但也可以不與文件系統(tǒng)的對(duì)象直接對(duì)應(yīng),它們可以是真實(shí)的模塊但沒有具體的表述。更新關(guān)于命名空間包的說明可以參考 PEP 420。
命名空間包的 __path__ 屬性與常規(guī)包不同,其使用自定義的迭代器類型,遍歷所有包含該命令空間包的路徑。如果他們父包的路徑(或者高階包的 sys.path )改變,它將在下次試圖導(dǎo)入時(shí)在該包中自動(dòng)重新搜索包部分。
如有如下目錄結(jié)構(gòu):
.
├── bar-package
│ └── nsp
│ └── bar.py
└── foo-package
└── nsp
└── foo.py
則 nsp 即可以是一個(gè)命名空間包,以下是測(cè)試代碼(記得用 Python 3.3 及更高版本運(yùn)行測(cè)試):
import sys sys.path.extend(['foo-package', 'bar-package']) import nsp import nsp.bar import nsp.foo print(nsp.__path__) # 輸出: # _NamespacePath(['foo-package/nsp', 'bar-package/nsp'])
命名空間包具有如下特性:
1、優(yōu)先級(jí)最低,在已有版本所有的 import 規(guī)則之后
2、包中不必再包含 __init__.py 文件
3、可以導(dǎo)入并組織目錄分散的代碼
4、依賴于 sys.path 中從左到右的搜索順序
__import__
__import__ 函數(shù)可用于導(dǎo)入模塊,import 語句也會(huì)調(diào)用函數(shù)。其定義為:
__import__(name[, globals[, locals[, fromlist[, level]]]])
參數(shù)介紹:
- name (required): 被加載 module 的名稱
- globals (optional): 包含全局變量的字典,該選項(xiàng)很少使用,采用默認(rèn)值 global()
- locals (optional): 包含局部變量的字典,內(nèi)部標(biāo)準(zhǔn)實(shí)現(xiàn)未用到該變量,采用默認(rèn)值 - local()
- fromlist (Optional): 被導(dǎo)入的 submodule 名稱
- level (Optional): 導(dǎo)入路徑選項(xiàng),Python 2 中默認(rèn)為 -1,表示同時(shí)支持 absolute import 和 relative import。Python 3 中默認(rèn)為 0,表示僅支持 absolute import。如果大于 0,則表示相對(duì)導(dǎo)入的父目錄的級(jí)數(shù),即 1 類似于 '.',2 類似于 '..'。
使用示例:
# import spam spam = __import__('spam') # import spam.ham spam = __import__('spam.ham') # from spam.ham import eggs, sausage as saus _temp = __import__('spam.ham', fromlist=['eggs', 'sausage']) eggs = _temp.eggs saus = _temp.sausage
模塊緩存
在執(zhí)行模塊導(dǎo)入時(shí),Python 的導(dǎo)入系統(tǒng)會(huì)首先嘗試從 sys.modules 查找。sys.modules 中是所有已導(dǎo)入模塊的一個(gè)緩存,包括中間路徑。即,假如 foo.bar.baz 被導(dǎo)入,那么,sys.modules 將包含進(jìn)入 foo,foo.bar 和 foo.bar.baz 模塊的緩存。其實(shí)一個(gè) dict 類型,每個(gè)鍵都有自己的值,對(duì)應(yīng)相應(yīng)的模塊對(duì)象。
導(dǎo)入過程中,首先在 sys.modules 中查找模塊名稱,如果存在,則返回該模塊并結(jié)束導(dǎo)入過程。如果未找到模塊名稱,Python 將繼續(xù)搜索模塊(從 sys.path 中查找并加載)。sys.modules 是可寫的,刪除一個(gè)鍵會(huì)使指定模塊的緩存實(shí)現(xiàn),下次導(dǎo)入時(shí)又將重新搜索指定的模塊,這類似于模塊的 reload。
需要注意的是,如果保持模塊對(duì)象引用,并使 sys.modules 中緩存失效,然后再重新導(dǎo)入指定的模塊,則這兩個(gè)模塊對(duì)象是不相同的。而相比之下,importlib.reload() 重新加載模塊時(shí),會(huì)使用相同的模塊對(duì)象,并通過重新運(yùn)行模塊代碼簡(jiǎn)單地重新初始化模塊內(nèi)容。
imp 與 importlib 模塊
imp 模塊提供了一些 import 語句內(nèi)部實(shí)現(xiàn)的接口。例如模塊查找(find_module)、模塊加載(load_module)等等(模塊的導(dǎo)入過程會(huì)包含模塊查找、加載、緩存等步驟)??梢杂迷撃K來簡(jiǎn)單實(shí)現(xiàn)內(nèi)建的 __import__ 函數(shù)功能:
import imp import sys def __import__(name, globals=None, locals=None, fromlist=None): # 首先從緩存中查找 try: return sys.modules[name] except KeyError: pass # 如果模塊緩存中沒有,則開始從 sys.path 中查找模塊 fp, pathname, description = imp.find_module(name) # 如何找到模塊則將其載入 try: return imp.load_module(name, fp, pathname, description) finally: if fp: fp.close()
importlib 模塊在 python 2.7 被創(chuàng)建,并且僅包含一個(gè)函數(shù):
importlib.import_module(name, package=None)
這個(gè)函數(shù)是對(duì) __import__ 的封裝,以用于更加便捷的動(dòng)態(tài)導(dǎo)入模塊。例如用其實(shí)現(xiàn)相對(duì)導(dǎo)入:
import importlib # 類似于 'from . import b' b = importlib.import_module('.b', __package__)
從 python 3 開始,內(nèi)建的 reload 函數(shù)被移到了 imp 模塊中。而從 Python 3.4 開始,imp 模塊被否決,不再建議使用,其包含的功能被移到了 importlib 模塊下。即從 Python 3.4 開始,importlib 模塊是之前 imp 模塊和 importlib 模塊的合集。
惰性導(dǎo)入
前邊介紹的大部分內(nèi)容都是為實(shí)現(xiàn)惰性導(dǎo)入做鋪墊,其他的小部分內(nèi)容僅是延伸而已(就是隨便多介紹了點(diǎn)內(nèi)容)。惰性導(dǎo)入即延遲模塊導(dǎo)入,在真正用到模塊時(shí)才執(zhí)行模塊的導(dǎo)入動(dòng)作,如果模塊不被使用則導(dǎo)入動(dòng)作永遠(yuǎn)不會(huì)發(fā)生。
惰性導(dǎo)入的需求還是很常見的。一般推薦模塊僅在頂層導(dǎo)入,而有時(shí)候在頂層導(dǎo)入模塊并非最好的選擇。比如,一個(gè)模塊僅在一個(gè)函數(shù)或者類方法中用到時(shí),則可使用局部導(dǎo)入(在局部作用域中執(zhí)行導(dǎo)入),使得僅在函數(shù)或方法被執(zhí)行時(shí)才導(dǎo)入模塊,這樣可以避免在頂層名字空間中引入模塊變量。再比如,在我工作所負(fù)責(zé)的項(xiàng)目中,需要用到 pandas 包,而 pandas 包導(dǎo)入了會(huì)占用一些內(nèi)存(不是很多,但也不算少,幾十兆的樣子),所以當(dāng)不會(huì)用到 pandas 包時(shí),我們希望他不被導(dǎo)入。我們自己實(shí)現(xiàn)的一些包在載入時(shí)會(huì)很耗時(shí)(因?yàn)橐x取配置等等,在導(dǎo)入時(shí)就會(huì)耗時(shí)幾秒到十幾秒的樣子),所以也極其需要惰性導(dǎo)入的特性。
下面是惰性導(dǎo)入的簡(jiǎn)單實(shí)現(xiàn),可供參考:
import sys from types import ModuleType class LazyModuleType(ModuleType): @property def _mod(self): name = super(LazyModuleType, self).__getattribute__("__name__") if name not in sys.modules: __import__(name) return sys.modules[name] def __getattribute__(self, name): if name == "_mod": return super(LazyModuleType, self).__getattribute__(name) try: return self._mod.__getattribute__(name) except AttributeError: return super(LazyModuleType, self).__getattribute__(name) def __setattr__(self, name, value): self._mod.__setattr__(name, value) def lazy_import(name, package=None): if name.startswith('.'): if not package: raise TypeError("relative imports require the 'package' argument") level = 0 for character in name: if character != '.': break level += 1 if not hasattr(package, 'rindex'): raise ValueError("'package' not set to a string") dot = len(package) for _ in range(level, 1, -1): try: dot = package.rindex('.', 0, dot) except ValueError: raise ValueError("attempted relative import beyond top-level " "package") name = "{}.{}".format(package[:dot], name[level:]) return LazyModuleType(name)
總結(jié)
到此這篇關(guān)于Python中模塊導(dǎo)入的文章就介紹到這了,更多相關(guān)Python模塊導(dǎo)入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
參考資料
- https://docs.python.org/3/reference/import.html
- https://github.com/nipy/nitime
- https://github.com/mnmelo/lazy_import
相關(guān)文章
利用Python找出刪除自己微信的好友并將他們自動(dòng)化刪除
你是否有微信被刪了好友不自知,還傻傻的給對(duì)方發(fā)消息,結(jié)果出現(xiàn)了下圖中那尷尬的一幕的經(jīng)歷呢?其實(shí)我們可以用Python提前把他們找出來并自動(dòng)化刪除避免尷尬的2023-01-01python,Django實(shí)現(xiàn)的淘寶客登錄功能示例
這篇文章主要介紹了python,Django實(shí)現(xiàn)的淘寶客登錄功能,結(jié)合實(shí)例形式分析了Django框架基于淘寶接口的登錄功能相關(guān)操作技巧,需要的朋友可以參考下2019-06-06詳解pandas如何去掉、過濾數(shù)據(jù)集中的某些值或者某些行?
這篇文章主要介紹了pandas如何去掉、過濾數(shù)據(jù)集中的某些值或者某些行?,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05python一維表轉(zhuǎn)二維表的實(shí)現(xiàn)示例
本文主要介紹了python一維表轉(zhuǎn)二維表的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07Python實(shí)現(xiàn)監(jiān)控遠(yuǎn)程主機(jī)實(shí)時(shí)數(shù)據(jù)的示例詳解
這篇文章主要為大家詳細(xì)介紹了Python如何使用Socket庫和相應(yīng)的第三方庫來監(jiān)控遠(yuǎn)程主機(jī)的實(shí)時(shí)數(shù)據(jù),比如CPU使用率、內(nèi)存使用率、網(wǎng)絡(luò)帶寬等,感興趣的可以了解一下2023-04-04