1、模塊
一、什麼是模塊?
一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的後綴。
二、爲什麼要使用模塊?
簡而言之,模塊經過使用自包含的變量的包,也就是所謂的命名空間提供了將部件組織爲系統的簡單的方法。在一個模塊文件的頂層定義的全部的變量名都成了被導入的模塊對象的屬性。正如咱們本書中前一部分中見到的那樣,導入給予了對模塊的全局做用域中的變量名的讀取權。也就是說,在模塊導入時,模塊文件的全局做用域變成了模塊對象的命名空間。最後,Python的模塊容許將獨立的文件鏈接成一個更大的程序系統。更確切地說,從抽象的視角來看,模塊至少有三個角色,以下所示。
2.1 代碼重用
就像在第3章中討論過的那樣,模塊能夠在文件中永久保存代碼。不像在Python交互提示模式輸入的代碼,當退出Python時,代碼就會消失,而在模塊文件中的代碼是永久的。你能夠按照須要任意次數地從新載入和從新運行模塊。除了這一點以外,模塊仍是定義變量名的空間,被認做是屬性,能夠被多個外部的客戶端引用。
2.2 系統命名空間的劃分
模塊仍是在Python中最高級別的程序組織單元。從根本上講,它們不過是變量名的軟件包。模塊將變量名封裝進了自包含的軟件包,這一點對避免變量名的衝突頗有幫助。若是不精確導入文件的話,咱們是不可能看到另外一個文件中的變量名。事實上,全部的一切都「存在於」模塊文件中,執行的代碼以及建立的對象都毫無疑問地封裝在模塊之中。正是因爲這一點,模塊是組織系統組件的自然的工具。
2.3 實現共享服務和數據
從操做的角度來看,模塊對實現跨系統共享的組件是很方便的,並且只須要一個拷貝便可。例如,若是你須要一個全局對象,這個對象會被一個以上的函數或文件使用,你能夠將它編寫在一個模塊中以便可以被多個客戶端導入。爲了真正理解Python系統中模塊的角色,那麼,咱們須要偏離主題來探索Python程序的通用結構。python
三、如何使用模塊?
3.一、import
實例文件:spam.py ,文件名:spam.py ,模塊名:spam
windows
#spam.py print('from the spam.py') money=1000 def read1(): print('spam->read1',money) def read2(): print('spam->read2') read1() def change(): global money #聲明爲全局名稱空間 money=0 #從新給money賦值
3.1.一、模塊能夠包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行
import語句是能夠在程序中的任意位置使用的,且針對同一個模塊很import屢次,爲了防止你重複導入,python的優化手段是:第一次導入後就將模塊名加載到內存了,後續的import語句僅是對已經加載大內存中的模塊對象增長了一次引用,不會從新執行模塊內的語句,以下:
緩存
import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是隻打印一, import spam #次'from the spam.py',固然其餘的頂級代碼也都被執行了,只不過沒有顯示效果。 import spam import spam
執行結果:
bash
from the spam.py
咱們能夠從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否須要從新導入。
3.1.二、每一個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當作全局名稱空間,這樣咱們在編寫本身的模塊時,就不用擔憂咱們定義在本身模塊中全局變量會在被導入時,與使用者的全局變量衝突。
#測試一:money與spam.money不衝突
架構
import spam money = 123123 print(spam.money) -執行結果: from the spam.py 1000 #測試二:read1與spam.read1不衝突 import spam def read1(): print("---------->") spam.read1()
執行結果:
app
from the spam.py spam->read1 1000
#測試三:執行spam.change()操做的全局變量money仍然是spam中的 import spam money = 999 spam.change() #更改的是spam裏的money print(money)
執行結果:ide
from the spam.py 999
3.1.三、總結:
首次導入模塊spam時會作三件事:
第一件事:建立名稱空間,用來存放spam.py中定義的名字;
第二件事:基於剛剛建立的名稱空間來執行spam.py;
第三件事:建立名字spam指向該名稱空間,spam.名字的操做,都是以spam.py爲準。
3.1.四、爲模塊名起別名,至關於m1=1;m2=m1
函數
import spam as sm print(sm.money)
爲已經導入的模塊起別名的方式對編寫可擴展的代碼頗有用,假設有兩個模塊xmlreader.py和csvreader.py,它們都定義了函數read_data(filename):用來從文件中讀取一些數據,但採用不一樣的輸入格式。能夠編寫代碼來選擇性地挑選讀取模塊,例如:
工具
if file_format == 'xml': import xmlreader as reader elif file_format == 'csv': import csvreader as reader data=reader.read_date(filename)
3.1.五、在一行導入多個模塊
性能
import os,sys,re
3.二、from... import...
3.2.一、對比import spam,會將源文件的名稱空間'spam'帶到當前名稱空間中,使用時必須是spam.名字的方式而from 語句至關於import,也會建立新的名稱空間,可是將spam中的名字直接導入到當前的名稱空間中,在當前名稱空間中,直接使用名字就能夠了。
from spam import read1,read2
這樣在當前位置直接使用read1和read2就行了,執行時,仍然以spam.py文件全局名稱空間
測試一:導入的函數read1,執行時仍然回到spam.py中尋找全局變量money
#test.py from spam import read1 read1() 執行結果: from the spam.py spam->read1 1000
測試二:導入的函數read2,執行時須要調用read1(),仍然回到spam.py中找read1()
#test.py from spam import read2 def read1(): print('==========') read2()
執行結果:
from the spam.py spam->read2 spam->read1 1000
測試三:導入的函數read1,被當前位置定義的read1覆蓋掉了
#test.py from spam import read1 def read1(): print("===========>") read1() #重名時,會覆蓋導入的名稱
執行結果:
from the spam.py ===========>
須要特別強調的一點是:python中的變量賦值不是一種存儲操做,而只是一種綁定關係,以下:
from spam import money,read1 money = 999 #將當前位置的名字money綁定到了999 print(money) #打印當前的名字 read1() #調用spam.py中的名字money,仍然爲1000
執行結果:
from the spam.py 999 spam->read1 1000
3.2.二、也支持as
from spam import read1 as read
3.2.三、也支持導入多行
from spam import (money,read1,read2)
3.2.四、from spam import * 把spam中全部的不是如下劃線(_)開頭的名字都導入到當前位置,大部分狀況下咱們的python程序不該該使用這種導入方式,由於「*」你不知道你導入什麼名字,頗有可能會覆蓋掉你以前已經定義的名字。並且可讀性極差,在交互式環境中導入時沒有問題。
from spam import * #將模塊spam中全部的名字都導入到當前名稱空間 print(money) print(read1) print(read2) print(change)
執行結果:
from the spam.py 1000 <function read1 at 0x00000000028CA9D8> <function read2 at 0x00000000028CA950> <function change at 0x00000000028CAA60>
可使用__all__來控制*(用來發布新版本)
在spam.py中新增一行
__all__=['money','read1'] #這樣在另一個文件中用from spam import *就這能導入列表中規定的兩個名字,不在列表裏的沒法調用
3.2.五、考慮到性能的緣由,每一個模塊只被導入一次,放入字典sys.modules中,若是你改變了模塊的內容,你必須重啓程序,python不支持從新加載或卸載以前導入的模塊。有的同窗可能會想到直接從sys.modules中刪除一個模塊不就能夠卸載了嗎,注意了,你刪了sys.modules中的模塊對象仍然可能被其餘程序的組件所引用,於是不會被清楚。特別的對於咱們引用了這個模塊中的一個類,用這個類產生了不少對象,於是這些對象都有關於這個模塊的引用。
3.三、把模塊當作腳本執行
咱們能夠經過模塊的全局變量__name__來查看模塊名:
當作腳本運行:
__name__ 等於'__main__'
當作模塊導入:
__name__=
做用:用來控制.py文件在不一樣的應用場景下執行不一樣的邏輯
if __name__ == '__main__': print(__name__) #被當成腳本文件執行時 if __name__ == '__main__': print("此時被當作腳本去執行")
執行結果:
__main__
此時被當作腳本去執行
被當成模塊導入的時候:
import spam
#當導入模塊時,就會執行模塊,模塊有兩個打印效果,此時spam不是當成腳本運行了,so,就不會執行下面的代碼。
執行結果:
spam #打印了模塊名
3.四、模塊搜索路徑
python解釋器在啓動時會自動加載一些模塊,可使用sys.modules查看,在第一次導入某個模塊時(好比spam),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存)。若是有則直接引用,若是沒有,解釋器則會查找同名的內建模塊,若是尚未找到就從sys.path給出的目錄列表中依次尋找spam.py文件。
因此總結模塊的查找順序是:
內存中已經加載的模塊--->內置模塊--->sys.path路徑中包含的模塊
須要特別注意的是:咱們自定義的模塊名不該該與系統內置模塊重名。雖然每次都說,可是仍然會有人不停的犯錯。
在初始化後,python程序能夠修改sys.path,路徑放到前面的優先於標準庫被加載。
import sys sys.path.append('/a/b/c/d') sys.path.insert(0,'/x/y/z') #排在前的目錄,優先被搜索
注意:搜索時按照sys.path中從左到右的順序查找,位於前的優先被查找,sys.path中還可能包含.zip歸檔文件和.egg文件,python會把.zip歸檔文件當成一個目錄去處理。
#首先製做歸檔文件:zip module.zip foo.py bar.py
import sys sys.path.append('module.zip') import foo,bar
#也可使用zip中目錄結構的具體位置
sys.path.append('module.zip/lib/python')
至於.egg文件是由setuptools建立的包,這是按照第三方python庫和擴展時使用的一種常見格式,.egg文件實際上只是添加了額外元數據(如版本號,依賴項等)的.zip文件。
須要強調的一點是:只能從.zip文件中導入.py,.pyc等文件。使用C編寫的共享庫和擴展塊沒法直接從.zip文件中加載(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中加載文件不會建立.pyc或者.pyo文件,所以必定要事先建立他們,來避免加載模塊是性能降低。
3.五、編譯Python文件
爲了提升模塊的加載速度,Python緩存編譯的版本,每一個模塊在__pycache__目錄的以module.version.pyc的形式命名,一般包含了python的版本號,如在CPython版本3.6,關於spam.py的編譯版本將被緩存成__pycache__/spam.cpython-36.pyc,這種命名約定容許不一樣的版本,不一樣版本的Python編寫模塊共存。
Python檢查源文件的修改時間與編譯的版本進行對比,若是過時就須要從新編譯。這是徹底自動的過程。而且編譯的模塊是平臺獨立的,因此相同的庫能夠在不一樣的架構的系統之間共享,即pyc使一種跨平臺的字節碼,相似於JAVA火.NET,是由python虛擬機來執行的,可是pyc的內容跟python的版本相關,不一樣的版本編譯後的pyc文件不一樣,2.5編譯的pyc文件不能到3.6上執行,而且pyc文件是能夠反編譯的,於是它的出現僅僅是用來提高模塊的加載速度的。
提示:
一、模塊名區分大小寫,foo.py與FOO.py表明的是兩個模塊;
二、你可使用-O或者-OO轉換python命令來減小編譯模塊的大小;
3.六、標準模塊
python提供了一個標準模塊庫,一些模塊被內置到解釋器中,這些提供了不屬於語言核心部分的操做的訪問,但它們是內置的,不管是爲了效率仍是提供對操做系統原語的訪問。這些模塊集合是依賴於底層平臺的配置項,如winreg模塊只能用於windows系統。特別須要注意的是,sys模塊內建在每個python解釋器
sys.ps1
sys.ps2
這倆只在命令行有效,得出的結果,標識瞭解釋器是在交互式模式下。
變量sys.path是一個決定了模塊搜索路徑的字符串列表,它從環境變量PYTHONOATH中初始化默認路徑,若是PYTHONPATH沒有設置則從內建中初始化值,咱們能夠修改它
sys.path.append: import os os.path.normpath(path) #規範化路徑,轉換path的大小寫和斜槓 a='/Users/jieli/test1/\\\a1/\\\\aa.py/../..' print(os.path.normpath(a))
打印結果:
\Users\jieli\test1
#具體應用
import os,sys possible_topdir = os.path.normpath(os.path.join( os.path.abspath(__file__), os.pardir, #上一級 os.pardir, os.pardir )) sys.path.insert(0,possible_topdir)
3.七、dir()函數
內建函數dir是用來查找模塊中定義的名字,返回一個有序字符串列表
import spam dir(spam)
若是沒有參數,dir()列舉出當前定義的名字
dir()不會列舉出內建函數或者變量的名字,它們都被定義到了標準模塊builtin中,能夠列舉出它們,
import builtins dir(builtins)