導入模塊時除了使用模塊名進行導入,還可使用目錄名進行導入。例如,在sys.path路徑下,有一個dir1/dir2/mod.py模塊,那麼在任意位置處均可以使用下面這種方式導入這個模塊。html
import dir1.dir2.mod from dir1.dir2.mod import XXX
一個實際一點的示例,設置PYTHONPATH環境變量爲d:\pypath
,而後在此目錄下建立以上目錄和mod.py文件:python
set PYTHONPATH="D:\pypath" mkdir d:\pypath\dir1\dir2 echo print("mod.py") >d:\pypath\dir1\dir2\mod.py echo x=3 >>d:\pypath\dir1\dir2\mod.py # 進入交互式python >>> import dir1.dir2.mod mod.py >>> dir1.dir2.mod.x 3
注1:在python3.3版本及更高版本是能夠導入成功的,可是在python3.3以前的版本將失敗,由於缺乏__init__.py
文件,稍後會解釋該文件
注2:頂級目錄dir1必須位於sys.path列出的路徑搜索列表下測試
若是輸出dir1和dir2,將會看到它們的是模塊對象,且是名稱空間。ui
>>> import dir1.dir2.mod mod.py >>> dir1 <module 'dir1' (namespace)> >>> dir1.dir2 <module 'dir1.dir2' (namespace)> >>> dir1.dir2.mod <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
這種模塊+名稱空間
的形式就是包(嚴格地說是包的一種形式),也就是說dir1是包,dir2也是包,這種方式是包的導入形式。包主要用來組織它裏面的模塊。spa
從上面的結果也能夠看出,包也是模塊,因此能使用模塊的地方就能使用包。例以下面的代碼,能夠像導入模塊同樣直接導入包dir2,包和模塊的區別在於它們的組織形式不同,模塊可能位於包內,僅此而已。操作系統
import dir1.dir2 from dir1 import dir2
另外,導入dir1.dir2.mod時,它聲明的模塊變量名爲dir1,而不是dir1.dir2.mod,可是導入的對象卻包含了3個模塊:dir一、dir1.dir2以及dir1.dir2.mod。以下:code
>>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'dir1'] >>> for key in sys.modules: ... if key.startswith("dir1"): ... print(key,":",sys.modules[key]) ... dir1 : <module 'dir1' (namespace)> dir1.dir2 : <module 'dir1.dir2' (namespace)> dir1.dir2.mod : <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
上面的dir1和dir1.dir2目前是空包,或者說是空模塊(再一次強調,包就是模塊)。但並不意味着它們對應的模塊對象是空的,由於模塊是對象,只要是對象就會有屬性。例如,dir1包有以下屬性:htm
>>> dir(dir1) ['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'dir2']
之因此稱爲空包,是由於它們如今僅提供了包的組織功能,並且它們是目錄,而不像py文件同樣,是實實在在的能夠編寫模塊代碼的地方。換句話說,包如今是目錄文件,而不是真正的模塊文件。對象
爲了讓包"真正的"成爲模塊,須要在每一個包所表明的目錄下加入一個__init__.py
文件,它表示讓這個目錄格式的模塊(也就是包)像py文件同樣能夠寫模塊代碼,只不過這些模塊代碼是寫入__init__.py
中的。固然,模塊文件中容許沒有任何內容,因此__init__.py
文件也能夠是空文件,它僅表示讓包成爲真正的模塊文件。blog
每次導入包的時候,若是有__init__.py
文件,將會自動執行這個文件中的代碼,就像模塊文件同樣,事實上它就是讓目錄表明的包變成模塊的,甚至能夠說它就是包所對應的模塊文件(見下面示例),因此也能夠認爲__init__.py
是包的初始化文件。在python3.3以前,這個文件必須存在,不然就會報錯,由於它不認爲目錄是有效的模塊。
如今,在dir1和dir2下分別建立空文件__init__.py
:
type nul>d:\pypath\dir1\__init__.py type nul>d:\pypath\dir1\dir2\__init__.py
如今目錄的層次格式以下:
λ tree /f d:\pypath D:\PYPATH └─dir1 │ __init__.py └─dir2 mod.py __init__.py
再去執行導入操做,並輸出包dir1和dir2。
>>> import dir1.dir2.mod mod.py >>> dir1 <module 'dir1' from 'd:\\pypath\\dir1\\__init__.py'> >>> dir1.dir2 <module 'dir1.dir2' from 'd:\\pypath\\dir1\\dir2\\__init__.py'> >>> dir1.dir2.mod <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
從輸出結果中不難看出,包dir1和dir1.dir2是模塊,且它們的模塊文件是各自目錄下的__init__.py
。
實際上,包分爲兩種:名稱空間模塊、普通模塊。名稱空間包是沒有__init__.py
文件的,普通包是有__init__.py
文件的。不管是哪一種,它都是模塊。
既然包是模塊,而__init__.py
文件是包的模塊文件,這個文件中應該寫入什麼代碼?答案是能夠寫入任何代碼,咱們只需把它看成一個模塊對待就能夠。不過,包既然是用來組織模塊的,真正的功能性屬性應該儘可能寫入到它所組織的模塊文件中(也就是示例中的mod.py)。
但有一項__all__
是應該在__init__.py
文件中定義的,它是一個列表,用來控制from package import *
使用*
導入哪些模塊文件。這裏的*
並不是像想象中那樣會導入包中的全部模塊文件,而是隻導出__all__
列表中指定的模塊文件。
例如,在dir1.dir2包下有mod1.py、mod2.py、mod3.py和mod4.py,若是在dir2/__init__.py
文件中寫入:
__all__ = ["mod1", "mod2", "mod3"]
則執行:
from dir1.dir2 import *
不會導入mod4,而是隻導入mod1-mod3。
若是不設置__all__
,則from dir1.dir2 import *
不會導入該包下的任何模塊,但會導入dir1和dir1.dir2。
嚴格地說,只有當某個模塊設置了__path__
屬性時,纔算是包,不然只算是模塊。這是包的絕對嚴格定義。
__path__
屬性是一個路徑列表(可迭代對象便可,但一般用列表),和sys.path
相似,該列表中定義了該包的初始化模塊文件__init__.py
的路徑。
只要導入的是一個包(不管是名稱空間包仍是普通包),首先就會設置該屬性,默認導入目錄時該屬性會初始化當前目錄,而後去該屬性列出的路徑下搜索__init__.py
文件對包進行初始化。默認狀況下因爲__init__.py
文件後執行,在此文件中能夠繼續定義或修改__path__
屬性,使得python會去找其它路徑下的__init__.py
對模塊進行初始化。
如下是默認初始化後的__path__
值:
>>> import dir1.dir2 >>> dir1.dir2.__path__ ['d:\\pypath\\dir1\\dir2'] >>> import dir1.dir3 >>> dir1.dir3 <module 'dir1.dir3' (namespace)> >>> dir1.dir3.__path__ _NamespacePath(['d:\\pypath\\dir1\\dir3'])
通常來講,幾乎不會設置__path__
屬性。
import和from導入時有多種語法可用,這兩個語句的導入方式和導入普通模塊的方式是同樣的:import導入時須要使用前綴名稱去引用,from導入時是賦值到當前程序的同名全局變量中。若是不瞭解,請看前一篇文章:python模塊導入細節。
假設如今有以下目錄結構,且d:\pypath位於sys.path列表中:
$ tree -f d:\pypath d:\pypath └── dir1 ├── __init__.py └── dir2 ├── __init__.py └── mod.py
只導入包:
import dir1 # 導入包dir1 import dir1.dir2 # 導入包dir1.dir2 from dir1 import dir2 # 導入包dir1.dir2
導入某個模塊:
import dir1.dir2.mod from dir1.dir2 import mod
若是dir2/__init__.py
中設置了__all__
,則下面的導入語句會導入已設置的模塊:
from dir1.dir2 import *
注意,只支持上面這種from...import *
語法,不支持import *
。
導入模塊中的屬性,好比變量x:
from dir1.dir2.mod import x
注:若是容許,不要使用相對路徑導入,很容易出錯,特別是對新手而言。使用絕對路徑導入,並將包放在sys.path的某個路徑下就能夠。
假設如今有以下目錄結構:
$ tree -f d:\pypath d:\pypath └── dir1 ├── __init__.py ├── dir4 │ ├── __init__.py │ ├── c2.py │ └── c1.py ├── dir3 │ ├── __init__.py │ ├── b3.py │ ├── b2.py │ └── b1.py └── dir2 ├── __init__.py ├── a4.py ├── a3.py ├── a2.py └── a1.py
在dir1.dir2.a1模塊文件中想要導入dir1.dir3.b2模塊,能夠在a1.py中使用下面兩種方式導入:
import dir1.dir3.b2 from dir1.dir2. import b2
上面的導入方式是使用絕對路徑進行導入的,只要使用絕對路徑,都是從sys.path開始搜索的。例如,上面是從sys.path下搜索dir1,再依次搜索dir1.dir3.b2。
python還支持包的相對路徑的導入,只要使用.
或..
便可,就像操做系統上的相對路徑同樣。使用相對路徑導入時不會搜索sys.path。
相對路徑導入方式只有from...import
支持,import
語句不支持,且只有使用.
或..
的纔算是相對路徑,不然就是絕對路徑,就會從sys.path下搜索。
例如,在a1.py中導入dir1.dir3.b2:
from ..dir3 import b2
注意,必須不能直接python a1.py
執行這個文件,這樣會報錯:
from ..dir3 import b2 ValueError: attempted relative import beyond top-level package
報錯緣由稍後解釋。如今在交互式模式下導入,或者使用python -m dir1.dir2.a1
的方式執行。
>>> import dir1.dir2.a1
如下幾個示例都如此測試。
在a1.py中導入包dir3:
from .. import dir3
在a1.py中導入dir1.dir2.a2,也就是同目錄下的a2.py:
from . import a2
導入模塊的屬性,如變量x:
from ..dir3.b2 import x from .a2 import x
前面說過一個相對路徑導入時的錯誤:
from ..dir3 import b2 ValueError: attempted relative import beyond top-level package
dir3明明在dir1下,在路徑相對上,dir3確實是a1.py的../dir3
,但執行python a1.py
爲何會報錯?
from ..dir3 import b2
這是由於文件系統路徑並不真的表明包的相對路徑,當在dir1/a1.py中使用..dir3
,python並不知道包dir1的存在,由於沒有將它導入,沒有聲明爲模塊變量,一樣,也不知道dir2的存在,僅僅只是根據語句知道了dir3的存在。但由於使用了相對路徑,不會搜索sys.path,因此它的相對路徑邊界只在本文件。因此,下面的導入也是錯誤的:
from . import a2
實際上,更標準的解釋是,當py文件做爲可執行程序文件執行時,它所在的模塊名爲__main__
,即__name__
爲__main__
,但它並不是一個包,而是一個模塊文件,對它來講沒有任何相對路徑可言。
解決方法是顯式導入它們的父包,讓python記錄它的存在,只有這樣才能使用..
:
python -m dir1.dir2.a2
還有幾個常見的相對路徑導入錯誤:
from .a3 import x
錯誤:
ModuleNotFoundError: No module named '__main__.a3'; '__main__' is not a package
緣由是同樣的,py文件做爲可執行程序文件執行時,它所在的模塊名爲__main__
,它並不是一個包。
最後,建議在條件容許的狀況下,使用絕對路徑導入,而不是相對路徑。
經過包的導入方式也支持別名。例如:
from dir1.dir2.a2 import x as xx print(xx) import dir1.dir2.a2 as a2 print(a2.x) from dir1.dir2 import a2 as a22 print(a22.x)