官方手冊:https://docs.python.org/3/tutorial/modules.htmlhtml
python源代碼文件按照功能能夠分爲兩種類型:python
例如文件a.py和b.py在同一目錄下,它們的內容分別是:linux
# b.py x="var x in module b" y=5 # a.py: import b import sys print(b.x) print(b.y)
a.py導入其它文件(b.py)後,就可使用b.py文件中的屬性(如變量、函數等)。這裏,a.py就是可執行文件,b.py就是模塊文件,但模塊名爲b,而非b.py。windows
python提供了一些標準庫,是預約義好的模塊文件,例如上面的sys模塊。app
在此有幾個注意點,在後面會詳細解釋:less
b.x
、b.y
import abc.b
。下一篇文章會詳細解釋包的導入方式在a.py中導入模塊b的時候,python會作一系列的模塊文件路徑搜索操做:b.py在哪裏?只有找到它才能讀取、運行(裝載)該模塊。ssh
在任何一個python程序啓動時,都會將模塊的搜索路徑收集到sys模塊的path屬性中(sys.path
)。當python須要搜索模塊文件在何處時,首先搜索內置模塊,若是不是內置模塊,則搜索sys.path中的路徑列表,搜索時會從該屬性列出的路徑中按照從前向後的順序進行搜索,而且只要找到就當即中止搜索該模塊文件(也就是說不會後搜索的同名模塊覆蓋先搜索的同名模塊)。函數
例如,在a.py文件中輸出一下這個屬性的內容:性能
# a.py: import sys print(sys.path)
結果:單元測試
['G:\\pycode', 'C:\\Program Files (x86)\\Python36-32\\python36.zip', 'C:\\Program Files (x86)\\Python36-32\\DLLs', 'C:\\Program Files (x86)\\Python36-32\\lib', 'C:\\Program Files (x86)\\Python36-32', 'C:\\Users\\malong\\AppData\\Roaming\\Python\\Python36\\site-packages', 'C:\\Program Files (x86)\\Python36-32\\lib\\site-packages']
python模塊的搜索路徑包括幾個方面,按照以下順序搜索:
G:\\pycode
PYTHONPATH
所設置的路徑(若是定義了該環境變量,則從左向右的順序搜索)須要注意,上面sys.path的結果中,除了.zip
是一個文件外,其它的搜索路徑全都是目錄,也就是從這些目錄中搜索模塊X的文件X.py是否存在。
這個目錄是最早搜索的,且是python自動搜索的,無需對此進行任何設置。從交互式python程序終輸出sys.path的結果:
>>> sys.path ['', 'C:\\WINDOWS\\system32', 'C:\\Program Files (x86)\\Python36-32\\Lib\\idlelib', 'C:\\Program Files (x86)\\Python36-32\\python36.zip', 'C:\\Program Files (x86)\\Python36-32\\DLLs', 'C:\\Program Files (x86)\\Python36-32\\lib', 'C:\\Program Files (x86)\\Python36-32', 'C:\\Users\\malong\\AppData\\Roaming\\Python\\Python36\\site-packages', 'C:\\Program Files (x86)\\Python36-32\\lib\\site-packages']
其中第一個''
表示的就是程序所在目錄。
注意程序所在目錄和當前目錄是不一樣的。例如,在/tmp/目錄下執行/pycode中的a.py文件
cd /tmp python /pycode/a.py
其中/tmp爲當前目錄,而/pycode是程序文件a.py所在的目錄。若是a.py中導入b.py,那麼將首先搜索/pycode,而不是/tmp。
這個變量中能夠自定義一系列的模塊搜索路徑列表,這樣能夠跨目錄搜索(另外一種方式是設置.pth文件)。但默認狀況下這個環境變量是未設置的。
在windows下,設置PYTHONPATH環境變量的方式:命令行中輸入:SystemPropertiesAdvanced-->環境變量-->系統環境變量新建
若是是多個路徑,則使用英文格式的分號分隔。如下是臨時設置當前命令行窗口的PYTHONPATH:
set PYTHONPATH='D:\pypath; d:\pypath1'
在unix下,設置PYTHONPATH環境變量的方式,使用冒號分隔多個路徑:另外,必須得export導出爲環境變量
export PYTHONPATH=/tmp/pypath1:/tmp/pypath2
若是要永久生效,則寫入配置文件中:
echo 'export PYTHONPATH=/tmp/pypath1:/tmp/pypath2' >/etc/profile.d/pypth.sh chmod +x /etc/profile.d/pypth.sh source /etc/profile.d/pypth.sh
在Linux下,標準庫的路徑通常是在/usr/lib/pythonXXX/下(XXX表示python版本號),此目錄下有些分了子目錄。
例如:
['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']
其中/usr/lib/python3.5和其內的幾個子目錄都是標準庫的搜索路徑。
注意其中/usr/lib/python35.zip,它是ZIP文件組件,當定義此文件爲搜索路徑時,將自動解壓縮該文件,並今後文件中搜索模塊。
Windows下根據python安裝位置的不一樣,標準庫的路徑不一樣。若是以默認路徑方式安裝的python,則標準庫路徑爲C:\\Program Files (x86)\\Python36-32
及其分類的子目錄。
能夠將自定義的搜索路徑放進一個.pth文件中,每行一個搜索路徑。而後將.pth文件放在python安裝目錄或某個標準庫路徑內的sitepackages目錄下便可。
這是一種替換PYTHONPATH的友好方式,由於不一樣操做系統設置環境變量的方式不同,而以文件的方式記錄是全部操做系統都通用的。
例如,windows下,在python安裝目錄C:\\Program Files (x86)\\Python36-32
下新增一個mypath.pth文件,內容以下:
d:\pypath1 d:\pypath2
再去輸出sys.path,將能夠看到這兩個路徑已經放進了搜索列表中。
除了上面環境變量和.pth文件,還能夠直接修改sys.path或者site.getsitepackages()的結果。
例如,在import導入sys模塊以後,能夠修改sys.path,向這個列表中添加其它搜索路徑,這樣以後導入其它模塊的時候,也會搜索該路徑。
例如:
import sys sys.path.append('d:\\pypath3') print(sys.path)
sys.path的最後一項將是新添加的路徑。
python的import是在程序運行期間執行的,並不是像其它不少語言同樣是在編譯期間執行。也就是說,import能夠出如今任何地方,只有執行到這個import行時,纔會執行導入操做。且在import某個模塊以前,沒法訪問這個模塊的屬性。
python在import導入模塊時,首先搜索模塊的路徑,而後編譯並執行這個模塊文件。雖然歸納起來只有兩個過程,但實際上很複雜。
前文已經解釋了import的模塊搜索過程,因此這裏大概介紹import的其它細節。
之前面的a.py中導入模塊文件b.py爲例:
import b
import導入模塊時,搜索到模塊文件b.py後:
1.首先在內存中爲每一個待導入的模塊構建module類的實例:模塊對象。這個模塊對象目前是空對象,這個對象的名稱爲全局變量b。
注意細節:module類的對象,變量b。
輸出下它們就知道:
print(b) print(type(b))
輸出結果:
<module 'b' from 'g:\\pycode\\b.py'> <class 'module'>
由於b是全局變量,因此當前程序文件a.py中不能從新對全局變量b進行賦值,這會使導入的模塊b被丟棄。例如,下面是錯誤的:
import b b=3 print(b.x) # 已經沒有模塊b了
另外,由於import導入時是將模塊對象賦值給模塊變量,因此模塊變量名不能是python中的一些關鍵字,好比if、for等,這時會報錯。雖然模塊文件名能夠爲list、keys等這樣的內置函數名,但這會致使這些內置函數不可用,由於根據變量查找的做用域規則,首先查找全局變量,再查找內置做用域。也就是說,模塊文件的文件名不能是這些關鍵字、也不該該是這些內置函數名。
File "g:/pycode/new.py", line 11 import if ^ SyntaxError: invalid syntax
2.構造空模塊實例後,將編譯、執行模塊文件b.py,並按照必定的規則將一些結果放進這個模塊對象中。
注意細節,編譯、執行b.py、將結果保存到模塊對象中。
模塊第一次被導入的時候,會進行編譯,並生成.pyc字節碼文件,而後python執行這個pyc文件。當模塊被再次導入時,若是檢查到pyc文件的存在,且和源代碼文件的上一次修改時間戳mtime徹底對應(也就是說,編譯後源代碼沒有進行過修改),則直接裝載這個pyc文件並執行,不會再進行額外的編譯過程。固然,若是修改過源代碼,將會從新編譯獲得新的pyc文件。
注意,並不是全部的py文件都會生成編譯獲得的pyc文件,對於那些只執行一次的程序文件,會將內存中的編譯結果在執行完成後直接丟棄(多數時候如此,但仍有例外,好比使用compileall模塊能夠強制編譯成pyc文件),但模塊會將內存中的編譯結果持久化到pyc文件中。另外,運行字節碼pyc文件並不會比直接運行py文件更快,執行它也同樣是一行行地解釋、執行,惟一快的地方在於導入裝載的時候無需從新編譯而已。
執行模塊文件(已完成編譯)的時候,按照通常的執行流程執行:一行一行地、以代碼塊爲單元執行。通常地,模塊文件中只用來聲明變量、函數等屬性,以便提供給導入它的模塊使用,而不該該有其餘任何操做性的行爲,好比print()操做不該該出如今模塊文件中,但這並不是強制。
總之,執行完模塊文件後,這個模塊文件將有一個本身的全局名稱空間,在此模塊文件中定義的變量、函數等屬性,都會記錄在此名稱空間中。
最後,模塊的這些屬性都會保存到模塊對象中。因爲這個模塊對象賦值給了模塊變量b,因此經過變量b能夠訪問到這個對象中的屬性(好比變量、函數等),也就是模塊文件內定義的全局屬性。
假設a.py中導入了模塊b和模塊sys,在b.py中也導入了模塊sys,但python默認對某個模塊只會導入一次,若是a.py中先導入sys,再導入b,那麼導入b並執行b.py的時候,會發現sys已經導入了,不會再去導入sys。
實際上,python執行程序的時候,會將全部已經導入的模塊放進sys.module屬性中,這是一個dict,能夠經過下面的方式查看已導入的模塊名:
>>> import sys >>> list(sys.module.keys())
若是某個程序文件中屢次使用import(或from)導入同一個模塊,雖然不會報錯,但實際上仍是直接使用內存中已裝載好的模塊對象。
例如,b.py中x=3,導入它以後修改該值,而後再次導入,發現b.x並不會發生改變:
import b print(b.x) # 3 b.x=33 print(b.x) # 33 import b print(b.x) # 33
可是python提供了reload進行屢次重複導入的方法,見後文。
import導入時,可使用as
關鍵字指定一個別名做爲模塊對象的變量,例如:
import b as bb bb.x=3 print(bb.x)
這時候模塊對象將賦值給變量bb,而不是b,b此時再也不是模塊對象變量,而僅僅只是模塊名。使用別名並不會影響性能,由於它僅僅只是一個賦值過程,只不過是從原來的賦值對象變量b變爲變量bb而已。
import語句是導入模塊中的全部屬性,而且訪問時須要使用模塊變量來引用。例如:
import b print(b.x)
除了import,還有一個from語句,表示從模塊中導入部分指定的屬性,且使得能夠直接使用這些屬性的名稱來引用這些屬性,而不須要加上模塊變量名。例如原來import導入時訪問變量x使用b.x
,from導入時只需使用x便可。實際上,from導入更應該稱爲屬性的再次賦值(拷貝)。
例如,b.py中定義了變量x、y、z,同時定義了函數f()和g(),在a.py中導入這個模塊文件,但只導入x變量和f函數:
# a.py文件內容: from b import x,f print(x) f() # b.py文件內容: x=3 y=4 z=5 def f(): print("function f in b.py") def g(): print("function g in b.py")
注意上面a.py中引用模塊b中屬性的方式沒有加上b.X
,而是直接使用x和f()來引用。這和import是不同的。至於from和import導入時的變量名稱細節,在下面的內容中會詳細解釋。
雖然from語句只導入模塊的部分屬性,但實際上仍然會完整地執行整個模塊文件。
一樣的,from語句也能夠指定導入屬性的變量別名,例如,將b.py中的屬性x賦值給xx,將y賦值給yy:
from b import x as xx,y as yy print(xx) print(yy)
from語句還有一個特殊導入統配符號*
,它表示導入模塊中的全部屬性。
# a.py文件: from b import * print(x,y,z) f() g()
多數時候,不該該使用from *
的方式,由於咱們可能會忘記某個模塊中有哪些屬性拷貝到了當前文件,特別是多個from *
時可能會出現屬性覆蓋的問題。
不管時import仍是from,都只導入一次模塊,但使用reload()能夠強制從新裝載模塊。
reload()是imp模塊中的一個函數,因此要使用imp.reload()以前,必須先導入imp。
from imp import reload reload(b)
reload()是一個函數,它的參數是一個已經成功被導入過的模塊變量(若是使用了別名,則應該使用別名做爲reload的參數),也就是說該模塊必須在內存中已經有本身的模塊對象。
reload()會從新執行模塊文件,並將執行獲得的屬性徹底覆蓋到原有的模塊對象中。也就是說,reload()會從新執行模塊文件,但不會在內存中創建新的模塊對象,因此原有模塊對象中的屬性可能會被修改。
例如,模塊文件b.py中x=3,導入b模塊,修改其值爲33,而後reload這個模塊,會發現值從新變回了3。
import b print(b.x) # 3 b.x=33 print(b.x) # 33 from imp import reload reload(b) print(b.x) # 3
有時候reload()頗有用,可讓程序無需重啓就執行新的代碼。例如,在python的交互式模式下導入模塊b,而後修改python源碼,再reload導入:
>>> import b >>> b.x 3 # 不要關掉交互式解釋器,直接修改源代碼中的b=3333 >>> from imp import reload >>> reload(b) <module 'b' from 'G:\\pycode\\b.py'> >>> b.x 3333
但正由於reload()重載模塊會改變原始的值,這多是很危險的行爲,必定要清楚地知道它是在幹什麼。
import導入時,模塊對象中的屬性有本身的名稱空間,而後將整個模塊對象賦值給模塊變量。
例如,在a.py中導入b:
import b print(b.x)
這個過程惟一和當前文件a.py做用域有關的就是模塊對象變量b,b.py中聲明的屬性和當前文件無任何關係。不管是訪問仍是修改,都是直接修改這個模塊對象自身做用域中的值。因此,只要模塊變量b不出現衝突問題,能夠放心地修改模塊b中的屬性。
另外一方面,由於每一個進程都有本身的內存空間,因此在a.py、c.py中都導入b時,a.py中修改b的屬性值不會影響c.py中導入的屬性,a.py和c.py中模塊對象所保存的屬性都是執行b.py後獲得的,它們相互獨立。
from導入模塊時,會先執行完模塊文件,而後將指定的部分屬性從新賦值給當前程序文件的同名全局變量。
例如,在模塊文件b.py中定義了x、y、z變量和f()、g()函數:
# b.py: x=3 y=4 b=5 def f(): print("function f in b.py") def g(): print("function g in b.py")
當在a.py中導入b模塊時,若是隻導入x、y和f():
# a.py: from b import x, y, f
實際上的行爲是構造模塊對象後,將這個模塊對象對應的名稱空間中的屬性x、y和f從新賦值給a.py中的變量x、y和f,而後丟棄整個模塊對象以及整個名稱空間。換句話說,b再也不是一個有效的模塊變量(因此和import不同),來自b的x,y,z,f和g也都被丟棄。
這裏有幾個細節,須要詳細解釋清楚,只有理解了才能搞清楚它們是怎麼生效的。
假設如今模塊文件b.py的內容爲,而且a.py中導入x,y,f屬性:
# b.py: x=3 y=[1,2] z=5 def f(): print("function f in b.py") def g(): print("function g in b.py") # a.py: from b import x,y,f
首先在執行模塊文件b.py時,會構造好本身的模塊對象,而且模塊對象有本身的名稱空間(做用域),模塊對象構造完成後,它的名稱空間大體以下:
而後python會在a.py的全局做用域內建立和導入屬性同名的全局變量x,y和f,而且經過賦值的方式將模塊的屬性賦值給這些全局變量,也就是:
x = b.x y = b.y f = b.f
上面的b只是用來演示,實際上變量b是不存在的。
賦值完成後,咱們和構造的整個模塊對象就失去聯繫了,由於沒有變量b去引用這個對象。但須要注意,這個對象並無被刪除,僅僅只是咱們沒法經過b去找到它。
因此,如今的示意圖以下:
由於是賦值的方式傳值的,因此在a.py中修改這幾個變量的值時,是直接在模塊對象做用域內修改的:對於不可變對象,將在此做用域內建立新對象,對於可變對象,將直接修改原始對象的值。
另外一方面,因爲模塊對象一直保留在內存中,下次繼續導入時,將直接使用該模塊對象。對於import和from,是直接使用該已存在的模塊對象,對於reload,是覆蓋此模塊對象。
例如,在a.py中修改不可變對象x和可變對象y,以後import或from時,可變對象的值都會隨之改變,由於它們使用的都是原來的模塊對象:
from b import x,y x=33 y[0]=333 from b import x,y print((x,y)) # 輸出(3, [333, 2]) import b print((b.x,b.y)) # 輸出(3, [333, 2])
from導入時,因爲b再也不是模塊變量,因此沒法再使用reload(b)去重載對象。若是想要重載,只能先import,再reload:
from b import x,y ...CODE... # 想要重載b import b from imp import reload reload(b)
內置函數dir可用於列出某模塊中定義了哪些屬性(全局名稱空間)。完整的說明見help(dir)
。
import b dir(b)
輸出結果:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f', 'g', 'x', 'y', 'z']
可見,模塊的屬性中除了本身定義的屬性外,還有一些內置的屬性,好比上面以__
開頭和結尾的屬性。
若是dir()不給任何參數,則輸出當前環境下定義的名稱屬性:
>>> import b >>> x=3 >>> aaa=333 >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'aaa', 'b', 'x']
每一個屬性都對應一個對象,例如x對應的是int對象,b對應的是module對象:
>>> type(x) <class 'int'> >>> type(b) <class 'module'>
既然是對象,那麼它們都會有本身的屬性。例如:
>>> dir(x) ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
因此,也能夠直接dir某個模塊內的屬性:
import b dir(b.x) dir(b.__name__)
dir()不會列出內置的函數和變量,若是想要輸出內置的函數和變量,能夠去標準模塊builtins中查看,由於它們定義在此模塊中:
import builtins dir(buildins)
除了內置dir()函數能夠獲取屬性列表(名稱空間),對象的__dict__
屬性也能夠獲取對象的屬性字典(名稱空間),它們的結果不徹底同樣。詳細說明參見dir()和__dict__屬性區別。
總的來講,獲取對象M中一個自定義的屬性age,有如下幾種方法:
M.age M.__dict__['age'] sys.modules['M'].age getattr(M,'age')
前面說了,py文件分兩種:用於執行的程序文件和用於導入的模塊文件。當直接使用python a.py
的時候表示a.py是用於執行的程序文件,經過import/from方式導入的py文件是模塊文件。
__name__
屬性用來區分py文件是程序文件仍是模塊文件:
__main__
換句話說,__main__
表示的是當前執行程序文件的默認模塊名,想必學過其餘支持包功能的語言的人很容易理解:程序都須要一個入口,入口程序所在的包就是main包,在main包中導入其它包來組織整個程序。python也是如此,只不過它是隱式自動設置的。
對於python來講,由於隱式自動設置,該屬性就有了特殊妙用:直接在模塊文件中經過if __name__ == "__main__"
來判斷,而後寫屬於執行程序的代碼,若是直接用python執行這個文件,說明這個文件是程序文件,因而會執行屬於if代碼塊的代碼,若是是被導入,則是模塊文件,if代碼塊中的代碼不會被執行。
顯然,這是python中很是方便的單元測試方式。
例如,寫一個模塊文件,裏面包含一個函數,用來求給定序列的最大值和最小值:
def minmax(func,*args): res = args[0] for arg in args[1:]: if func(arg,res): res = arg return res def lessthan(x,y): return x < y def greatethan(x,y): return x > y # 測試代碼 if __name__ == "__main__": print(minmax(lessthan,3,6,2,1,4,5)) print(minmax(greatethan,3,6,2,1,4,5))