模塊(module)和 包(package)是Python用於組織大型程序的利器。html
模塊 是一個由 變量、函數、類 等基本元素組成的功能單元,設計良好的模塊一般是高內聚、低耦合、可複用、易維護的。包 是管理模塊的容器,它具備 可嵌套性:一個包能夠包含模塊和其餘包。從文件系統的視角來看,包就是目錄,模塊就是文件。python
從本質上講,一個模塊就是一個獨立的名字空間(namespace),單純的多個模塊只能構成扁平結構的名字空間集;而包的可嵌套性,使得多個模塊能夠呈現出多層次結構的名字空間樹。linux
若是要在一個模塊A中使用另外一個模塊B(即訪問模塊B的屬性),則必須首先 導入 模塊B。此時模塊A稱爲導入模塊(即importer),而模塊B稱爲被導入模塊(即importee)。shell
導入語句(import statement)有兩種風格:import <>
和from <> import
。對模塊的導入同時支持這兩種風格。緩存
1)導入模塊module(重命名爲name)markdown
import module [as name]
app
>>> import sys >>> sys.version '2.7.3 (default, Apr 10 2013, 05:46:21) \n[GCC 4.6.3]' >>> import sys as s >>> s.version '2.7.3 (default, Apr 10 2013, 05:46:21) \n[GCC 4.6.3]'
2)導入模塊module1(重命名爲name1),模塊module2(重命名爲name2),等等函數
import module1 [as name1], module2 [as name2], ...
佈局
>>> import sys, os >>> sys.platform, os.name ('linux2', 'posix') >>> import sys as s, os as o >>> (s.platform, o.name) ('linux2', 'posix')
3)從模塊module中導入屬性attribute(重命名爲name)ui
from module import attribute [as name]
>>> from sys import executable >>> executable '/usr/bin/python' >>> from sys import executable as exe >>> exe '/usr/bin/python'
4)從模塊module中導入屬性attribute1(重命名爲name1),屬性attribute2(重命名爲name2),等等
from module import attribute1 [as name1], attribute2 [as name2], ...
>>> from sys import platform, executable >>> platform, executable ('linux2', '/usr/bin/python') >>> from sys import platform as plf, executable as exe >>> plf, exe ('linux2', '/usr/bin/python')
5)從模塊module中導入屬性attribute1(重命名爲name1),屬性attribute2(重命名爲name2),等等
from module import (attribute1 [as name1], attribute2 [as name2], ...)
>>> from sys import (platform, executable) >>> platform, executable ('linux2', '/usr/bin/python') >>> from sys import (platform as plf, executable as exe) >>> plf, exe ('linux2', '/usr/bin/python')
6)從模塊module中導入全部屬性
from module import *
>>> from sys import * >>> platform, executable ('linux2', '/usr/bin/python')
如下是在Python程序中推薦使用的導入語句:
import module [as name]
(導入單個模塊)from module import attribute [as name]
(導入單個屬性)from module import attribute1 [as name1], attribute2 [as name2], ...
(導入較少屬性時,單行書寫)from module import (attribute1 [as name1], attribute2 [as name2], ...)
(導入較多屬性時,分行書寫)應當儘可能避免使用的導入語句是:
import module1 [as name1], module2 [as name2], ...
它會下降代碼的可讀性,應該用多個import module [as name]
語句代替。
from module import *
它會讓importer的名字空間變得不可控(極可能一團糟)。
一個 模塊 就是一個Python源碼文件。若是文件名爲mod.py,那麼模塊名就是mod。
模塊的導入和使用都是藉助模塊名來完成的,模塊名的命名規則與變量名相同。
模塊屬性 是指在模塊文件的全局做用域內,或者在模塊外部(被其餘模塊導入後)能夠訪問的全部對象名字的集合。這些對象名字構成了模塊的名字空間,這個名字空間其實就是全局名字空間(參考 名字空間與做用域)。
模塊的屬性由兩部分組成:固有屬性 和 新增屬性。能夠經過 M.__dict__ 或 dir(M) 來查看模塊M的屬性。
1)固有屬性
固有屬性 是Python爲模塊默認配置的屬性。
例如,新建一個空文件mod.py:
$ touch mod.py $ python ... >>> import mod # 導入模塊mod >>> mod.__dict__ # 模塊mod的屬性全貌 {'__builtins__': {...}, '__name__': 'mod', '__file__': 'mod.pyc', '__doc__': None, '__package__': None} >>> dir(mod) # 只查看屬性名 ['__builtins__', '__doc__', '__file__', '__name__', '__package__']
上述示例中,空模塊mod的全部屬性都是固有屬性,包括:
__builtins__
內建名字空間(參考 名字空間)__file__
文件名(對於被導入的模塊,文件名爲絕對路徑格式;對於直接執行的模塊,文件名爲相對路徑格式)__name__
模塊名(對於被導入的模塊,模塊名爲去掉「路徑前綴」和「.pyc後綴」後的文件名,即os.path.splitext(os.path.basename(__file__))[0]
;對於直接執行的模塊,模塊名爲__main__
)__doc__
文檔字符串(即模塊中在全部語句以前第一個未賦值的字符串)__package__
包名(主要用於相對導入,請參考 PEP 366)2)新增屬性
新增屬性 是指在模塊文件的頂層(top-level),由賦值語句(如import、=、def和class)建立的屬性。
例如,修改文件mod.py爲:
#!/usr/bin/env python # -*- coding: utf-8 -*- '''this is a test module''' import sys debug = True _static = '' class test_class(object): def say(self): pass def test_func(): var = 0
再次查看模塊mod的屬性:
>>> import mod >>> dir(mod) ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_static', 'debug', 'sys', 'test_class', 'test_func']
對比上一小節可知,除開固有屬性外的其餘屬性都是新增屬性,包括:
這些屬性的共同點是:它們都在模塊文件的頂層建立。相比之下,類方法say(在類test_class內部建立)和局部變量var(在函數test_func內部建立)都不在頂層,所以不在新增屬性之列。(做爲一個例子,'''this is a test module'''就是模塊mod的文檔字符串,即mod.__doc__
的值)
在 『導入語句』 中描述的基本語法能夠概括爲三類:
import module
在導入模塊(importer)中,能夠經過module.*的形式訪問模塊module中的全部屬性from module import attribute
只能經過名字attribute訪問模塊module中的指定屬性module.attributefrom module import *
能夠直接經過屬性名訪問模塊module中的全部 公有屬性換句話說,模塊的 公有屬性 就是那些能夠經過from module import *
被導出給其餘模塊直接使用的屬性。
模塊的公有屬性有如下特色:
__all__
,其中包含全部可導出的公有屬性的字符串名稱,從而實現對公有屬性的定製__all__
,那麼默認全部不如下劃線「_」開頭的屬性都是可導出的公有屬性以 『新增屬性』 中的mod.py爲例,沒有定義__all__
的公有屬性:
>>> dir() # 導入模塊mod前的名字空間 ['__builtins__', '__doc__', '__name__', '__package__'] >>> from mod import * # 導入模塊mod中的全部公有屬性 >>> dir() # 導入模塊mod後的名字空間 ['__builtins__', '__doc__', '__name__', '__package__', 'debug', 'sys', 'test_class', 'test_func']
對比導入模塊mod先後的狀況可知,模塊mod中的sys、debug、test_class和test_func都屬於公有屬性,由於它們的名字不以「_」開頭;而其餘以「_」開頭的屬性(包括全部「固有屬性」,以及「新增屬性」中的_static)都不在公有屬性之列。
若是在模塊文件mod.py中頂層的任何位置,增長定義一個特殊列表__all__ = ['sys', '_static', 'test_func']
(此時__all__
也是模塊屬性),那麼此時的公有屬性:
>>> dir() # 導入模塊mod前的名字空間 ['__builtins__', '__doc__', '__name__', '__package__'] >>> from mod import * # 導入模塊mod中的全部公有屬性 >>> dir() # 導入模塊mod後的名字空間 ['__builtins__', '__doc__', '__name__', '__package__', '_static', 'sys', 'test_func']
能夠看出,只有在__all__
中指定的屬性纔是公有屬性。
模塊能夠在命令行下被直接執行,以模塊mod(對應文件mod.py)爲例:
1)以腳本方式執行
python mod.py <arguments>
2)以模塊方式執行
python -m mod <arguments>
一個 包 就是一個含有__init__.py文件的目錄。
包與模塊之間的包含關係是:一個包能夠包含子包或子模塊,但一個模塊卻不能包含子包和子模塊。
與模塊名相似,包名的命名規則也與變量名相同。
此外,須要特別注意的是:若是在同一個目錄下,存在兩個同名的包和模塊,那麼導入時只會識別包,而忽略模塊。(參考 specification for packages 中的 『What If I Have a Module and a Package With The Same Name?』)
例如,在目錄dir下新建一個文件spam.py(即模塊spam),此時import spam
會導入模塊spam:
$ cd dir/ $ touch spam.py $ python ... >>> import spam >>> spam <module 'spam' from 'spam.py'>
若是在目錄dir下再新建一個含有__init__.py文件的目錄spam(即包spam),此時import spam
則會導入包spam(而再也不是模塊spam):
$ mkdir spam && touch spam/__init__.py $ python ... >>> import spam >>> spam <module 'spam' from 'spam/__init__.py'>
包屬性與模塊屬性很是類似,也分爲 固有屬性 和 新增屬性。
1)固有屬性
與模塊相比,包的 固有屬性 僅多了一個__path__
屬性,其餘屬性徹底一致(含義也相似)。
__path__
屬性即包的路徑(列表),用於在導入該包的子包或子模塊時做爲搜索路徑;修改一個包的__path__
屬性能夠擴展該包所能包含的子包或子模塊。(參考 Packages in Multiple Directories)
例如,在dir目錄下新建一個包pkg(包含一個模塊mod),顯然在包pkg中只能導入一個子模塊mod:
$ mkdir pkg && touch pkg/__init__.py $ touch pkg/mod.py $ python ... >>> import pkg.mod # 能夠導入子模塊mod >>> import pkg.mod_1 # 不能導入子模塊mod_1 Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named mod_1
若是在dir目錄下再新建一個包pkg_1(包含一個模塊mod_1):
$ mkdir pkg_1 && touch pkg_1/__init__.py $ touch pkg_1/mod_1.py
而且在pkg/__init__.py中修改包pkg的__path__
屬性:
print('before:', __path__) __path__.append(__path__[0].replace('pkg', 'pkg_1')) # 將「包pkg_1所在路徑」添加到包pkg的__path__屬性中 print('after:', __path__)
此時,在包pkg中就能夠導入子模塊mod_1(彷彿子模塊mod_1真的在包pkg中):
$ python ... >>> import pkg.mod # 能夠導入子模塊mod ('before:', ['pkg']) ('after:', ['pkg', 'pkg_1']) >>> import pkg.mod_1 # 也能夠導入子模塊mod_1
2)新增屬性
包的 新增屬性 包括兩部分:靜態的新增屬性和動態的新增屬性。
靜態的新增屬性是指:在__init__.py的頂層(top-level),由賦值語句(如import、=、def和class)建立的屬性。這部分與模塊的新增屬性一致。
動態的新增屬性是指:在執行導入語句後動態添加的新增屬性。具體而言,若是有一個導入語句導入了某個包pkg中的子模塊submod(或子包subpkg),那麼被導入的子模塊submod(或子包subpkg)將做爲一個屬性,被動態添加到包pkg的新增屬性當中。
以包含模塊mod的包pkg爲例:
>>> import pkg >>> dir(pkg) ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__'] >>> import pkg.mod # 該語句導入了包pkg中的模塊mod >>> dir(pkg) # mod成爲了包pkg的「動態的新增屬性」 ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'mod']
在公有屬性方面,包與模塊的行爲徹底一致,固然別忘了包還有 動態的新增屬性。
1)導入語句
加入包的概念之後,導入語句的風格(與僅有模塊時相比)不變,可是語法上有一些細微差別:用「.」來表示包與模塊之間的包含關係;可操做的對象擴充爲 包、模塊 和 屬性。
下面是涉及包時,一些典型的導入語句:
import package
import package.module
import package.subpackage
import package.subpackage.module
from packagae import module
from packagae import subpackagae
from packagae import subpackagae.module
或from packagae.subpackagae import module
from packagae.module import attribute
from packagae.subpackagae.module import attribute
2)__init__.py
關於包的__init__.py,有如下幾點總結:
__all__
、__path__
等)模塊與包的導入原理幾乎徹底一致,所以下面以模塊爲主進行討論,僅在有顯著差別的地方對包做單獨說明。
對於模塊M而言,根據導入語句的不一樣(指明瞭模塊M是否在一個包中),可能存在導入依賴的問題:
import M
模塊M不在一個包中,所以無導入依賴:直接以「M」爲 完整名(fully qualified name)導入模塊M
import A.B.M
或者from A.B import M
模塊M在一個子包B中,而子包B又在一個包A中,所以存在導入依賴:會首先以「A」爲 完整名 導入包A,接着以「A.B」爲 完整名 導入子包B,最後以「A.B.M」爲 完整名 導入模塊M。
一個模塊的導入過程主要分三步:搜索、加載 和 名字綁定。(具體參考 The import statement)
1)搜索
搜索 是整個導入過程的核心,也是最爲複雜的一步。對於被導入模塊M,按照前後順序,搜索的處理步驟爲:
import P.M
),則以P.__path__
爲搜索路徑進行查找;若是模塊M不在一個包中(如import M
),則以 sys.path 爲搜索路徑進行查找2)加載
正如 『搜索』 步驟中所述,對於找到的模塊M:若是M在緩存 sys.modules 中,則直接返回;不然,會加載M。
加載 是對模塊的初始化處理,包括如下步驟:
__name__
、__file__
、__package__
和__loader__
(對於包,則還有__path__
)有一點值得注意的是,加載不僅是發生在導入時,還能夠發生在 reload() 時。
3)名字綁定
加載完importee模塊後,做爲最後一步,import語句會爲 導入的對象 綁定名字,並把這些名字加入到importer模塊的名字空間中。其中,導入的對象 根據導入語句的不一樣有所差別:
import obj
,則對象obj能夠是包或者模塊from package import obj
,則對象obj能夠是package的子包、package的屬性或者package的子模塊from module import obj
,則對象obj只能是module的屬性根據 The import statement 中的描述,如下是導入原理對應的Python僞碼:
import sys import os.path def do_import(name): '''導入''' parent_pkg_name = name.rpartition('.')[0] if parent_pkg_name: parent_pkg = do_import(parent_pkg_name) else: parent_pkg = None return do_find(name, parent_pkg) def do_find(name, parent_pkg): '''搜索''' if not name: return None # step 1 if name in sys.modules: return sys.modules[name] else: # step 2 for finder in sys.meta_path: module = do_load(finder, name, parent_pkg) if module: return module # step 3 src_paths = parent_pkg.__path__ if parent_pkg else sys.path for path in src_paths: if path in sys.path_importer_cache: finder = sys.path_importer_cache[path] if finder: module = do_load(finder, name, parent_pkg) if module: return module else: # handled by an implicit, file-based finder else: finder = None for callable in sys.path_hooks: try: finder = callable(path) break except ImportError: continue if finder: sys.path_importer_cache[path] = finder elif os.path.exists(path): sys.path_importer_cache[path] = None else: sys.path_importer_cache[path] = # a finder which always returns None if finder: module = do_load(finder, name, parent_pkg) if module: return module raise ImportError def do_load(finder, name, parent_pkg): '''加載''' path = parent_pkg.__path__ if parent_pkg else None loader = finder.find_module(name, path) if loader: return loader.load_module(name) else: return None
正如 『導入過程』 中所述,sys.path是 不在包中的模塊(如import M)的「搜索路徑」。在這種狀況下,控制sys.path就能控制模塊的導入過程。
sys.path 是一個路徑名的列表,按照前後順序,其中的路徑主要分爲如下四塊:
os.environ['PYTHONPATH']
(相似shell環境變量PATH)爲了控制sys.path,能夠有三種選擇:
關於導入,還有一點很是關鍵:加載只在第一次導入時發生。這是Python特地設計的,由於加載是個代價高昂的操做。
一般狀況下,若是模塊沒有被修改,這正是咱們想要的行爲;但若是咱們修改了某個模塊,重複導入不會從新加載該模塊,從而沒法起到更新模塊的做用。有時候咱們但願在 運行時(即不終止程序運行的同時),達到即時更新模塊的目的,內建函數 reload() 提供了這種 從新加載 機制。
關鍵字reload
與import
不一樣:
import
是語句,而reload
是內建函數import
使用 模塊名,而reload
使用 模塊對象(即已被import語句成功導入的模塊)從新加載(reload(module))有如下幾個特色:
import M
語句導入的模塊M:調用reload(M)
後,M.x
爲 新模塊 的屬性x(由於更新M後,會影響M.x
的求值結果)from M import x
語句導入的屬性x:調用reload(M)
後,x
仍然是 舊模塊 的屬性x(由於更新M後,不會影響x
的求值結果)reload(M)
後,從新執行import M
(或者from M import x
)語句,那麼M.x
(或者x
)爲 新模塊 的屬性x嚴格來講,模塊(或包)的導入方式分爲兩種:絕對導入 和 相對導入。以上討論的導入方式都稱爲 絕對導入,這也是Python2.7的默認導入方式。相對導入是從Python2.5開始引入的,主要用於解決「用戶自定義模塊可能會屏蔽標準庫模塊」的問題(參考 Rationale for Absolute Imports)。
相對導入 使用前導的「.」來指示importee(即被導入模塊或包)與importer(當前導入模塊)之間的相對位置關係。相對導入 只能使用from <> import
風格的導入語句,import <>
風格的導入語句只能用於 絕對導入。(相對導入的更多細節,請參考 PEP 328)
例若有一個包的佈局以下:
pkg/ __init__.py subpkg1/ __init__.py modX.py modY.py subpkg2/ __init__.py modZ.py modA.py
假設當前在文件modX.py或subpkg1/__init__.py中(即當前包爲subpkg1),那麼下面的導入語句都是相對導入:
from . import modY # 從當前包(subpkg1)中導入模塊modY from .modY import y # 從當前包的模塊modY中導入屬性y from ..subpkg2 import modZ # 從當前包的父包(pkg)的包subpkg2中導入模塊modZ from ..subpkg2.modZ import z # 從當前包的父包的包subpkg2的模塊modZ中導入屬性z from .. import modA # 從當前包的父包中導入模塊modA from ..modA import a # 從當前包的父包的模塊modA中導入屬性a
與絕對導入不一樣,相對導入的導入原理比較簡單:根據 模塊的__name__
屬性 和 由「.」指示的相對位置關係 來搜索並加載模塊(參考 Relative Imports and __name__)。
因爲相對導入會用到模塊的__name__
屬性,而在直接執行的主模塊中,__name__
值爲__main__
(沒有包與模塊的信息),因此在主模塊中:儘可能所有使用絕對導入。
若是非要使用相對導入,也能夠在頂層包(top-level package)的外部目錄下,以模塊方式執行主模塊:python -m pkg.mod
(假設頂層包爲pkg,mod爲主模塊,其中使用了相對導入)。(具體參考 PEP 366)