從零開始學 Python 之模塊
前言
Python3 模塊
在前面的幾個文章中我們從腳本上是用 python 解釋器來編程,如果你從 Python 解釋器退出再進入,那麼你定義的所有的方法和變數就都消失了。
為此 Python 提供了一個辦法,把這些定義存放再文件中,為一些腳本或者互動式的解釋器實例使用,這個文件被稱為模塊。
模塊是一個包含所有你定義的函數和變數的文件,其後綴名是 .py 。模塊可以被別的程序引入,以便用該模塊中的函數等功能。這也是使用 python 標準庫的方法。
下面是一個使用 python 標準庫中模塊的例子。
importsys
print("命令行參數如下:")
foriinsys.argv:
print(i)
print("
Python 路徑為:", sys.path,"
")
執行結果如下所示:
$ 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"]
1、import sys 引入 python 標準庫中的 sys.py 模塊;這是引入某一模塊的方法。
2、sys.argv 是一個包含命令行參數的列表
3、sys.path 包含了一個 Python 解釋器自動查找所需模塊的路徑的列表。
import 語句
想使用 Python 源文件,只需在另一個源文件里執行 import 語句,語法如下:
importmodule1[, module2[,... moduleN]
當解釋器遇到 import 語句,如果模塊在當前的搜索路徑就會被導入。
搜索路徑是一個解釋器會先進行搜索的所有目錄的列表。如果想要導入模塊 support,需要把命令放在腳本的頂端。
support.py 文件代碼為:
#!/usr/bin/python3
# Filename: support.py
defprint_func( par ):
print("Hello : ", par)
return
test.py 引入 support 模塊:
# 導入模塊
importsupport
# 現在可以調用模塊里包含的函數了
support.print_func("Koc")
以上實例輸出結果:
Hello: Koc
一個模塊只會被導入一次,不管你執行了多少次 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)數列模塊
deffib(n):# 定義到 n 的斐波那契數列
a, b =,1
whileb
print(b, end=" ")
a, b = b, a+b
print()
deffib2(n):# 返回到 n 的斐波那契數列
result = []
a, b =,1
whileb
result.append(b)
a, b = b, a+b
returnresult
然後進入 Python 解釋器,使用下面的命令導入這個模塊:
importfibo
這樣做並沒有把直接定義在 fibo 中的函數名稱寫入到當前符號表裡,只是把模塊 fibo 的名字寫到了那裡。
可以使用模塊名稱來訪問函數:
>>> fibo.fib(1000)
1123581321345589144233377610987
>>> fibo.fib2(100)
[1,1,2,3,5,8,13,21,34,55,89]
>>> fibo.__name__
"fibo"
如果你打算經常使用一個函數,你可以把它賦給一個本地的名稱:
>>> fib = fibo.fib
>>> fib(500)
1123581321345589144233377
from..import 語句
Python 的 from 語句讓你從模塊中導入一個指定的部分到當前的命令空間中,語法如下:
frommodnameimportname1[, name2[, ... nameN]
例如,要導入模塊 fibo 的 fib 函數,使用如下語句:
>>>fromfiboimportfib, fib2
>>>fib(500)
1123581321345589144233377
這個聲明不會把整個 fibo 模塊導入到當前的命名空間中,它只會將 fibo 里的 fib 函數引入進來。
From…import* 語句
把一個模塊的所有內容全部導入到當前的命名空間也是可行的,只需使用如下聲明:
frommodnameimport*
這提供了一個簡單的方法來導入一個模塊中的所有項目。然而這種聲明不該被過多地使用。
深入模塊
模塊除了方法定義,還可以包括可執行的代碼。這些代碼一般用來初始化這個模塊。這些代碼只有在第一次被導入時才會被執行。每個模塊有各自獨立的符號表,在模塊內部為所有的函數當作全局符號表來使用。
所以,模塊的作者可以放心大膽的在模塊內部使用這些全局變數,而不用擔心把其他用戶的全局變數搞花。
從另一個方面,當你確實知道你在做什麼的話,你也可以通過 modname.itemname 這樣的表示法來訪問模塊內的函數。
模塊是可以導入其他模塊的。在一個模塊(或者腳本,或者其他地方)的最前面使用 import 來導入一個模塊,當然這只是一個慣例,而不是強制的。被導入的模塊的名稱將被放入當前操作的模塊的符號表中。
還有一種導入的方法,可以使用 import 直接把模塊內(函數,變數的)名稱導入到當前操作模塊。比如:
>>>fromfiboimportfib, fib2
>>>fib(500)
1123581321345589144233377
這種導入的方法不會把被導入的模塊的名稱放在當前的字元表中(所以在這個例子里,fibo 這個名稱是沒有定義的)。
這還有一種方法,可以一次性的把模塊中的所有(函數,變數)名稱都導入到當前模塊的字元表:
>>>fromfiboimport*
>>>fib(500)
1123581321345589144233377
這將把所有的名字都導入進來,但是那些由單一下劃線(_)開頭的名字不在此列。大多數情況,Python 程序員不使用這種方法,因為引入的其他來源的命名,很可能覆蓋了已有的定義。
name屬性
一個模塊被另一個程序第一次引入時,其主程序將運行。如果我們想在模塊被引入時,模塊中的某一程序不執行,我們可以用 _name_ 屬性來使該模塊自身運行時執行。
#!/usr/bin/python3
# Filename: using_name.py
if__name__ =="__main__":
print("程序自身在運行")
else:
print("我來自另一模塊")
運行輸出如下:
$ python using_name.py
程序自身在運行
$ python
>>> import using_name
我來自另一模塊
>>>
說明:每個模塊都有一個 _name_ 屬性,當其值時 "_main_"時,表明該模塊自身在運行,否則是被引入。
dir() 函數
內置的函數 dir() 可以找到模塊內定義的所有名稱。以一個字元串列表的形式返回:
>>>importfibo, 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 本身帶著一些標準的模塊庫,在 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 中的目錄來尋找這個包中包含的子目錄。
目錄只有包含一個叫做int.py 的文件才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜索路徑中的有效模塊。
最簡單的情況,放一個空的 :file:int.py 就可以了。當然這個文件中也可以包含一些初始化代碼或者為(將在後面介紹的)_all_ 變數賦值。
用戶可以每次只導入一個包裡面的特定模塊,比如:
還有一種導入子模塊的方法是:
fromsound.effectsimportecho
這同樣會導入子模塊:echo,並且他不需要那些冗長的前綴,所以他可以這樣使用:
echo.echofilter(input,output, delay =0.7, atten =4)
還有一種變化就是直接導入一個函數或者變數:
同樣的,這種方法會導入子模塊:echo,並且可以直接使用他的 echofilter() 函數:
echofilter(input, output, delay = 0.7, atten = 4)
注意當使用 from package import item 這種形式的時候,對應的 item 既可以是包裡面的子模塊(子包),或者包裡面定義的其他名稱,比如函數,類或者變數。
import 語法會首先把 item 當作要給包定義的名稱,如果沒找到,再試圖按照一個模塊去導入。如果還沒找到,恭喜,一個:exc:ImportError 異常被拋出了。
從一個包中導入
設想一下,如果我們使用 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 裡面定義的所有名字導入進來。並且他不會破壞掉我們在這句話之前導入的所有明確指定的模塊。看下這部分代碼:
這個例子中,在執行 from…import 前,包 sound.effects 中的 echo 和 surround 模塊都被導入到當前的命名空間中了。(當然如果定義了 _all_就更沒問題了)
通常我們並不主張使用*這種方法來導入模塊,因為這種方法經常會導致代碼的可讀性降低。不過這樣倒的確是可以省去不少敲鍵的功夫,而且一些模塊都設計成了只能通過特定的方法導入。
記住,使用from Package import specific_submodule這種方法永遠不會有錯。事實上,這也是推薦的方法。除非是你要導入的子模塊有可能和其他包的子模塊重名。
from.importecho
from..importformats
from..filtersimportequalizer
無論是隱式的還是顯式的相對導入都是從當前模塊開始的。主模塊的名字永遠是"_main_",一個 Python 應用程序的主模塊,應當總是使用絕對路徑引用。
包還提供一個額外的屬性 _path_。這是一個目錄列表,裡面每一個包含的目錄都有為這個包服務的 _init_.py,你得在其他 _init_.py 被執行前定義哦。可以修改這個變數,用來影響包含在包裡面的模塊和子包。
這個功能並不常用,一般用來擴展包裡面的模塊。
寫在最後
至此「從零開始學習 Python:模塊」就已結束。給自己三分鐘的時間,閉上眼來回想下,今天這篇你都學習到了什麼。
如果你按我說的去做,你會發現你只能回憶起一些內容,很難把全部的知識都記住。這是非常正常的情況,所以需要你溫故而知新,時常回顧。歡迎大家去關注公眾號「痴海」,本系列教程會首發於公眾號。


※敲黑板!Python與人工智慧到底是什麼關係?
※Python 中最快解壓 zip 文件的方法
TAG:Python |