Python3 模組


在前面的幾個章節中我們腳本上是用 python 解釋器來編程,如果你從 Python 解釋器退出再進入,那麼你定義的所有的方法和變數就都消失了。

為此 Python 提供了一個辦法,把這些定義存放在檔案中,為一些腳本或互動式的解釋器實例使用,這個檔案稱為模組。

模組是一個包含所有你定義的函數和變數的文件,其後綴名是.py。模組可以被別的程式引入,以使用該模組中的函數等功能。這也是使用 python 標準函式庫的方法。

下面是一個使用 python 標準函式庫中模組的範例。

#!/usr/bin/python3
# 文件名: using_sys.py

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']
  • 1、import sys 引入 python 標準庫中的 sys.py 模組;這是引入某一模組的方法。

  • 2、sys.argv 是一個包含命令列參數的清單。

  • 3、sys.path 包含了一個 Python 解釋器自動尋找所需模組的路徑的清單。


import 語句

想使用Python 原始文件,只需在另一個原始檔執行import 語句,語法如下:

import module1[, module2[,... moduleN]

當解釋器遇到import 語句,如果模組在目前的搜尋路徑就會被導入。

搜尋路徑是指解釋器會先進行搜尋的所有目錄的清單。如想要導入模組support,需要把指令放在腳本的頂端:

support.py 檔案程式碼為:

#!/usr/bin/python3
# Filename: support.py

def print_func( par ):
    print ("Hello : ", par)
    return

test.py 引入support 模組:

#!/usr/bin/python3
# Filename: test.py
 
# 导入模块
import support
 
# 现在可以调用模块里包含的函数了
support.print_func("php")

以上實例輸出結果:

$ python3 test.py 
Hello :  php

一個模組只會被導入一次,不管你執行了多少次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()

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

這個宣告不會把整個fib模組匯入到目前的命名空間中,它只會將fib裡的fibonacci單一引入到執行這個宣告的模組的全域符號表。



From…import* 語句

把一個模組的所有內容全都導入到目前的命名空間也是可行的,只需使用如下宣告:

from modname import *

這提供了一個簡單的方法來導入一個模組中的所有項目。然而這種聲明不該被過度使用。


深入模組

模組除了方法定義,還可以包含可執行的程式碼。這些程式碼一般用來初始化這個模組。這些程式碼只有在第一次被匯入時才會被執行。

每個模組有各自獨立的符號表,在模組內部為所有的函數當作全域符號表來使用。

所以,模組的作者可以放心大膽的在模組內部使用這些全域變量,而不用擔心把其他使用者的全域變數搞花。

從另一個方面,當你確實知道你在做什麼的話,你也可以透過 modname.itemname 這樣的表示法來存取模組內的函數。

模組是可以導入其他模組的。在一個模組(或腳本,或其他地方)的最前面使用 import 來導入一個模組,當然這只是一個慣例,而不是強制的。被導入的模組的名稱將被放入目前操作的模組的符號表中。

還有一種導入的方法,可以使用 import 直接把模組內(函數,變數的)名稱導入到目前操作模組。例如:

>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

這種導入的方法不會把被導入的模組的名稱放在目前的字元表中(所以在這個例子裡面,fibo 這個名稱是沒有定義的)。

這還有一種方法,可以一次的把模組中的所有(函數,變數)名稱都匯入到目前模組的字元表:

>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

這將把所有的名字都導入進來,但是那些由單一底線(_)開頭的名字不在此例。大多數情況, 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() 可以找到模块内定义的所有名称。以一个字符串列表的形式返回:
</p>
<pre>
>>> 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 本身帶著一些標準的模組庫,在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)不小心的影響搜尋路徑中的有效模組。

最簡單的情況,放一個空的 :file:__init__.py就可以了。當然這個檔案中也可以包含一些初始化程式碼或為(將在後面介紹的) __all__變數賦值。

使用者可以每次只導入一個套件裡面的特定模組,例如:

import sound.effects.echo

這將會導入子模組:mod:song.effects.echo。 他必須使用全名去存取:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

還有一種導入子模組的方法是:

from sound.effects import echo

這同樣會導入子模組:mod:echo,而且他不需要那些冗長的前綴,所以他可以這樣使用:

echo.echofilter(input, output, delay=0.7, atten=4)

還有一種變化就是直接導入一個函數或變數:

from sound.effects.echo import echofilter

相同的,這種方法會導入子模組:mod:echo,並且可以直接使用他的:func: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 的檔案導入為模組:mod:echo還是:mod:Echo甚至:mod: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 *這種語法的時候,就*不會*導入包:mod:sound.effects裡的任何子模組。他只是把套件:mod:sound.effects和它裡面定義的所有內容導入進來(可能運行:file:__init__.py裡定義的初始化程式碼)。

這會把 :file:__init__.py裡面定義的所有名字導入進來。而且他不會破壞掉我們在這句話之前導入的所有明確指定的模組。看下這部分程式碼:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

這個範例中,在執行from...import前,包:mod:sound.effects中的echo和surround模組都被導入到目前的命名空間中了。 (當然如果定義了__all__就更沒問題了)

通常我們不會主張使用*這種方法來導入模組,因為這種方法經常會導致程式碼的可讀性降低。不過這樣倒的確是可以省去不少敲擊的功夫,而且有些模組都設計成了只能透過特定的方法導入。

記住,使用from Package import specific_submodule這種方法永遠不會出錯。事實上,這也是推薦的方法。除非是你要導入的子模組有可能和其他套件的子模組重名。

如果在結構中包是一個子包(例如這個例子中對於包:mod:sound來說),而你又想導入兄弟包(同級別的包)你就得使用導入絕對的路徑來導入。例如,如果模組:mod:sound.filters.vocoder 要使用套件:mod:sound.effects中的模組:mod:echo,你就要寫成 from sound.effects import echo。

from . import echo
from .. import formats
from ..filters import equalizer

無論是隱含的還是明確的相對導入都是從目前模組開始的。主模組的名字永遠是"__main__",一個Python應用程式的主模組,應當總是使用絕對路徑引用。

套件還提供一個額外的屬性,:attr:__path__。這是一個目錄列表,裡面每一個包含的目錄都有為這個包服務的:file:__init__.py,你得在其他:file:__init__.py被執行前定義哦。可以修改這個變量,用來影響包含在套件裡面的模組和子包。

這個功能並不常用,一般用來擴充包裡面的模組。