Python自動化運維之模塊與包的使用
一、模塊
1、什麽是模塊?
一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的後綴。
2、為何要使用模塊?
如果你退出python解釋器然後重新進入,那麽你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。
隨著程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用。
3、如何使用模塊?
3.1、import
#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.1、模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行
import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是只打印一, import spam #次‘from the spam.py‘,當然其他的頂級代碼也都被執行了,只不過沒有顯示效果。 import spam import spam
執行結果:
from the spam.py
我們可以從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入。
#測試一: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()
執行結果:
from the spam.py spam->read1 1000
#測試三:執行spam.change()操作的全局變量money仍然是spam中的 import spam money = 999 spam.change() #更改的是spam裏的money print(money)
執行結果:
from the spam.py 999
3.1.3、總結:
首次導入模塊spam時會做三件事:
第一件事:創建名稱空間,用來存放spam.py中定義的名字;
第二件事:基於剛剛創建的名稱空間來執行spam.py;
第三件事:創建名字spam指向該名稱空間,spam.名字的操作,都是以spam.py為準。
3.1.4、為模塊名起別名,相當於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.5、在一行導入多個模塊
import os,sys,re
3.2、from... import...
3.2.1、對比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.2、也支持as
from spam import read1 as read
3.2.3、也支持導入多行
from spam import (money,read1,read2)
3.2.4、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.5、考慮到性能的原因,每個模塊只被導入一次,放入字典sys.modules中,如果你改變了模塊的內容,你必須重啟程序,python不支持重新加載或卸載之前導入的模塊。有的同學可能會想到直接從sys.modules中刪除一個模塊不就可以卸載了嗎,註意了,你刪了sys.modules中的模塊對象仍然可能被其他程序的組件所引用,因而不會被清楚。特別的對於我們引用了這個模塊中的一個類,用這個類產生了很多對象,因而這些對象都有關於這個模塊的引用。
3.3、把模塊當做腳本執行
我們可以通過模塊的全局變量__name__來查看模塊名:
當做腳本運行:
__name__ 等於‘__main__‘
當做模塊導入:
__name__=
作用:用來控制.py文件在不同的應用場景下執行不同的邏輯
if __name__ == ‘__main__‘: print(__name__) #被當成腳本文件執行時 if __name__ == ‘__main__‘: print("此時被當做腳本去執行")
執行結果:
__main__
此時被當做腳本去執行
被當成模塊導入的時候:
import spam
#當導入模塊時,就會執行模塊,模塊有兩個打印效果,此時spam不是當成腳本運行了,so,就不會執行下面的代碼。
執行結果:
spam #打印了模塊名
3.4、模塊搜索路徑
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.5、編譯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文件是可以反編譯的,因而它的出現僅僅是用來提升模塊的加載速度的。
提示:
1、模塊名區分大小寫,foo.py與FOO.py代表的是兩個模塊;
2、你可以使用-O或者-OO轉換python命令來減少編譯模塊的大小;
3.6、標準模塊
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.7、dir()函數
內建函數dir是用來查找模塊中定義的名字,返回一個有序字符串列表
import spam dir(spam)
如果沒有參數,dir()列舉出當前定義的名字
dir()不會列舉出內建函數或者變量的名字,它們都被定義到了標準模塊builtin中,可以列舉出它們,
import builtins dir(builtins)
本文出自 “炫維” 博客,請務必保留此出處http://xuanwei.blog.51cto.com/11489734/1953467
Python自動化運維之模塊與包的使用