1. 程式人生 > >Python自動化運維之模塊與包的使用

Python自動化運維之模塊與包的使用

模塊與包使用 import from...import...

一、模塊
1、什麽是模塊?
一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的後綴。
2、為何要使用模塊?
如果你退出python解釋器然後重新進入,那麽你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。

隨著程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用。

3、如何使用模塊?
3.1、import

實例文件:spam.py ,文件名:spam.py ,模塊名:spam

#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語句是可以在程序中的任意位置使用的,且針對同一個模塊很import多次,為了防止你重復導入,python的優化手段是:第一次導入後就將模塊名加載到內存了,後續的import語句僅是對已經加載大內存中的模塊對象增加了一次引用,不會重新執行模塊內的語句,如下:

import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是只打印一,
import spam #次‘from the spam.py‘,當然其他的頂級代碼也都被執行了,只不過沒有顯示效果。
import spam
import spam

執行結果:

from the spam.py

我們可以從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入。

3.1.2、每個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局變量會在被導入時,與使用者的全局變量沖突。
#測試一: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自動化運維之模塊與包的使用