Lesson 028 —— python 模組
Lesson 028 —— python 模組
在前面的幾個章節中我們指令碼上是用 python 直譯器來程式設計,如果你從 Python 直譯器退出再進入,那麼你定義的所有的方法和變數就都消失了。
為此 Python 提供了一個辦法,把這些定義存放在檔案中,為一些指令碼或者互動式的直譯器例項使用,這個檔案被稱為模組。
模組是一個包含所有你定義的函式和變數的檔案,其後綴名是.py。模組可以被別的程式引入,以使用該模組中的函式等功能。這也是使用 python 標準庫的方法。
使用 python 標準模組例子如下:
import sys print('命令列引數如下:') for i in sys.argv: print(i) print('\n\nPython 路徑為:', sys.path, '\n')
結果如下:
$ python using_sys.py 引數1 引數2
命令列引數如下:
using_sys.py
引數1
引數2
Python 路徑為: ['/root', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages']
- import sys 引入 python 標準庫中的 sys.py 模組;這是引入某一模組的方法。
- sys.argv 是一個包含命令列引數的列表。
- sys.path 包含了一個 Python 直譯器自動查詢所需模組的路徑的列表。
import 語句
想使用 Python 原始檔,只需在另一個原始檔裡執行 import 語句,語法如下:
import module1[, module2[,... moduleN]
當直譯器遇到 import 語句,如果模組在當前的搜尋路徑就會被匯入。
一個模組只會被匯入一次,不管你執行了多少次import。這樣可以防止匯入模組被一遍又一遍地執行。
當我們使用import語句的時候,Python直譯器是怎樣找到對應的檔案的呢?
這就涉及到Python的搜尋路徑,搜尋路徑是由一系列目錄名組成的(是一個直譯器會先進行搜尋的所有目錄的列表),Python直譯器就依次從這些目錄中去尋找所引入的模組。
這看起來很像環境變數,事實上,也可以通過定義環境變數的方式來確定搜尋路徑。
搜尋路徑是在Python編譯或安裝的時候確定的,安裝新的庫應該也會修改。搜尋路徑被儲存在sys模組中的path變數。
>>> import sys
>>> sys.path
['', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages']
sys.path 輸出是一個列表,其中第一項是空串'',代表當前目錄(若是從一個指令碼中打印出來的話,可以更清楚地看出是哪個目錄),亦即我們執行python直譯器的目錄(對於指令碼的話就是執行的指令碼所在的目錄)。
因此若像我一樣在當前目錄下存在與要引入模組同名的檔案,就會把要引入的模組遮蔽掉。
瞭解了搜尋路徑的概念,就可以在指令碼中修改sys.path來引入一些不在搜尋路徑中的模組。
現在,在直譯器的當前目錄或者 sys.path 中的一個目錄裡面來建立一個fibo.py的檔案,程式碼如下:
# 斐波那契(fibonacci)數列模組
def fib(n): # 定義到 n 的斐波那契數列
a, b = 0, 1
while b < n:
print(b, end=' ')
a, b = b, a+b
print(a)
def fib2(n): # 返回到 n 的斐波那契數列
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
然後進入Python直譯器,使用下面的命令匯入這個模組:
>>> import fibo
這樣做並沒有把直接定義在fibo中的函式名稱寫入到當前符號表裡,只是把模組fibo的名字寫到了那裡。
可以使用模組名稱來訪問函式:
>>>fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果你打算經常使用一個函式,你可以把它賦給一個本地的名稱:
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
from ... import 語句
Python 的 from 語句讓你從模組中匯入一個指定的部分到當前名稱空間中,語法如下:
from modname import name1[, name2[, ... nameN]]
例如,要匯入模組 fibo 的 fib 函式,使用如下語句:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個宣告不會把整個fibo模組匯入到當前的名稱空間中,它只會將fibo裡的fib函式引入進來。
from ... import * 語句
把一個模組的所有內容全都匯入到當前的名稱空間也是可行的,只需使用如下宣告:
from modname import *
這種方法一次性的把模組中的所有(函式,變數)名稱都匯入到當前模組的字元表,這將把所有的名字都匯入進來,但是那些由單一下劃線(_
)開頭的名字不在此例。大多數情況, Python程式設計師不使用這種方法,因為引入的其它來源的命名,很可能覆蓋了已有的定義。
深入模組
模組除了方法定義,還可以包括可執行的程式碼。這些程式碼一般用來初始化這個模組。這些程式碼只有在第一次被匯入時才會被執行。
每個模組有各自獨立的符號表,在模組內部為所有的函式當作全域性符號表來使用。
所以,模組的作者可以放心大膽的在模組內部使用這些全域性變數,而不用擔心把其他使用者的全域性變數搞花。
從另一個方面,當你確實知道你在做什麼的話,你也可以通過 modname.itemname 這樣的表示法來訪問模組內的函式。
模組是可以匯入其他模組的。在一個模組(或者指令碼,或者其他地方)的最前面使用 import 來匯入一個模組,當然這只是一個慣例,而不是強制的。被匯入的模組的名稱將被放入當前操作的模組的符號表中。
__name__ 屬性
一個模組被另一個程式第一次引入時,其主程式將執行。如果我們想在模組被引入時,模組中的某一程式塊不執行,我們可以用__name__屬性來使該程式塊僅在該模組自身執行時執行。
if __name__ == '__main__':
print('程式自身在執行')
else:
print('我來自另一模組')
執行結果:
$ python using_name.py
程式自身在執行
$ python
>>> import using_name
我來自另一模組
說明: 每個模組都有一個__name__
屬性,當其值是 __main__
,表明該模組自身在執行,否則是被引入。
說明:`__name__
與 __main__
底下是雙下劃線, _ _
是這樣(去掉中間的那個空格)。
dir() 函式
內建的函式 dir() 可以找到模組內定義的所有名稱。以一個字串列表的形式返回:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
'_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder',
'call_tracing', 'callstats', 'copyright', 'displayhook',
'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'thread_info', 'version', 'version_info', 'warnoptions']
如果沒有給定引數,那麼 dir() 函式會羅列出當前定義的所有名稱:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir() # 得到一個當前模組中定義的屬性列表
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
>>> a = 5 # 建立一個新的變數 'a'
>>> dir()
['__builtins__', '__doc__', '__name__', 'a', 'sys']
>>>
>>> del a # 刪除變數名a
>>>
>>> dir()
['__builtins__', '__doc__', '__name__', 'sys']
標準模組
Python 本身帶著一些標準的模組庫。
有些模組直接被構建在解析器裡,這些雖然不是一些語言內建的功能,但是他卻能很高效的使用,甚至是系統級呼叫也沒問題。
這些元件會根據不同的作業系統進行不同形式的配置,比如 winreg 這個模組就只會提供給 Windows 系統。
應該注意到這有一個特別的模組 sys ,它內建在每一個 Python 解析器中。變數 sys.ps1 和 sys.ps2 定義了主提示符和副提示符所對應的字串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
包
包是一種管理 Python 模組名稱空間的形式,採用"點模組名稱"。
比如一個模組的名稱是 A.B, 那麼他表示一個包 A中的子模組 B 。
就好像使用模組的時候,你不用擔心不同模組之間的全域性變數相互影響一樣,採用點模組名稱這種形式也不用擔心不同庫之間的模組重名的情況。
這樣不同的作者都可以提供 NumPy 模組,或者是 Python 圖形庫。
不妨假設你想設計一套統一處理聲音檔案和資料的模組(或者稱之為一個"包")。
現存很多種不同的音訊檔案格式(基本上都是通過後綴名區分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一組不斷增加的模組,用來在不同的格式之間轉換。
並且針對這些音訊資料,還有很多不同的操作(比如混音,添加回聲,增加均衡器功能,建立人造立體聲效果),所以你還需要一組怎麼也寫不完的模組來處理這些操作。
這裡給出了一種可能的包結構(在分層的檔案系統中):
sound/ 頂層包
__init__.py 初始化 sound 包
formats/ 檔案格式轉換子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 聲音效果子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ filters 子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
在匯入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。
目錄只有包含一個叫做 __init__.py
的檔案才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜尋路徑中的有效模組。
最簡單的情況,放一個空的檔案: __init__.py
就可以了。當然這個檔案中也可以包含一些初始化程式碼或者為(將在後面介紹的) __all__
變數賦值。
使用者可以每次只匯入一個包裡面的特定模組,比如:
import sound.effects.echo
這將會匯入子模組:sound.effects.echo。 他必須使用全名去訪問:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
還有一種匯入子模組的方法是:
from sound.effects import echo
這同樣會匯入子模組: echo,並且他不需要那些冗長的字首,所以他可以這樣使用:
echo.echofilter(input, output, delay=0.7, atten=4)
還有一種變化就是直接匯入一個函式或者變數:
from sound.effects.echo import echofilter
同樣的,這種方法會匯入子模組: echo,並且可以直接使用他的 echofilter() 函式:
echofilter(input, output, delay=0.7, atten=4)
注意當使用from package import item
這種形式的時候,對應的item
既可以是包裡面的子模組(子包),或者包裡面定義的其他名稱,比如函式,類或者變數。
import
語法會首先把 item
當作一個包定義的名稱,如果沒找到,再試圖按照一個模組去匯入。如果還沒找到,恭喜,一個:exc:ImportError
異常被丟擲了。
反之,如果使用形如 import item.subitem.subsubitem
這種匯入形式,除了最後一項,都必須是包,而最後一項則可以是模組或者是包,但是不可以是類,函式或者變數的名字。
從一個包中匯入 *
設想一下,如果我們使用 from sound.effects import *
會發生什麼?
Python 會進入檔案系統,找到這個包裡面所有的子模組,一個一個的把它們都匯入進來。
但是很不幸,這個方法在 Windows平臺上工作的就不是非常好,因為Windows是一個大小寫不區分的系統。
在這類平臺上,沒有人敢擔保一個叫做 ECHO.py 的檔案匯入為模組 echo 還是 Echo 甚至 ECHO。
(例如,Windows 95就很討厭的把每一個檔案的首字母大寫顯示)而且 DOS 的 8+3 命名規則對長模組名稱的處理會把問題搞得更糾結。
為了解決這個問題,只能煩勞包作者提供一個精確的包的索引了。
匯入語句遵循如下規則:如果包定義檔案 __init__.py
存在一個叫做 __all__
的列表變數,那麼在使用 from package import *
的時候就把這個列表中的所有名字作為包內容匯入。
作為包的作者,可別忘了在更新包之後保證 __all__
也更新了啊。你說我就不這麼做,我就不使用匯入*
這種用法,好吧,沒問題,誰讓你是老闆呢。這裡有一個例子,在:file:sounds/effects/__init__.py
中包含如下程式碼:
__all__ = ["echo", "surround", "reverse"]
這表示當你使用from sound.effects import *
這種用法時,你只會匯入包裡面這三個子模組。
如果 __all__
真的沒有定義,那麼使用 from sound.effects import *
這種語法的時候,就不會匯入包 sound.effects
裡的任何子模組。他只是把包 sound.effects
和它裡面定義的所有內容匯入進來(可能執行__init__.py
裡定義的初始化程式碼)。
這會把 __init__.py
裡面定義的所有名字匯入進來。並且他不會破壞掉我們在這句話之前匯入的所有明確指定的模組。看下這部分程式碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
這個例子中,在執行from...import
前,包sound.effects
中的echo
和surround
模組都被匯入到當前的名稱空間中了。(當然如果定義了__all__
就更沒問題了)
通常我們並不主張使用*
這種方法來匯入模組,因為這種方法經常會導致程式碼的可讀性降低。不過這樣倒的確是可以省去不少敲鍵的功夫,而且一些模組都設計成了只能通過特定的方法匯入。
記住,使用from Package import specific_submodule
這種方法永遠不會有錯。事實上,這也是推薦的方法。除非是你要匯入的子模組有可能和其他包的子模組重名。
如果在結構中包是一個子包(比如這個例子中對於包sound
來說),而你又想匯入兄弟包(同級別的包)你就得使用匯入絕對的路徑來匯入。比如,如果模組sound.filters.vocoder
要使用包sound.effects
中的模組echo
,你就要寫成 from sound.effects import echo
。
from . import echo
from .. import formats
from ..filters import equalizer
無論是隱式的還是顯式的相對匯入都是從當前模組開始的。主模組的名字永遠是__main__
,一個Python應用程式的主模組,應當總是使用絕對路徑引用。
包還提供一個額外的屬性__path__
。這是一個目錄列表,裡面每一個包含的目錄都有為這個包服務的__init__.py
,你得在其他__init__.py
被執行前定義哦。可以修改這個變數,用來影響包含在包裡面的模組和子包。
這個功能並不常用,一般用來擴充套件包裡面的模組。