2024年6月16日 Python - 模組

流星<。)#)))≦發表於2024-06-17

模組

模組是一個包含所有你定義的函式和變數的檔案,其字尾名是 .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'] 

  • import sys 引入 python 標準庫中的 sys.py 模組;這是引入某一模組的方法
  • sys.argv 是一個包含命令列引數的列表
  • 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("Runoob")  # Hello :  Runoob


一個模組只會被匯入一次,不管你執行了多少次 import 。這樣可以防止匯入模組被一遍又一遍地執行。

當我們使用 import 語句的時候,Python 直譯器是怎樣找到對應的檔案的呢?

這就涉及到 Python 的搜尋路徑,搜尋路徑是由一系列目錄名組成的,Python 直譯器就依次從這些目錄中去尋找所引入的模組。

這看起來很像環境變數,事實上,也可以透過定義環境變數的方式來確定搜尋路徑。

搜尋路徑是在 Python 編譯或安裝的時候確定的,安裝新的庫應該也會修改。搜尋路徑被儲存在 sys 模組中的 path 變數

import sys

print(sys.path)

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

在其他地方匯入:

import fibo

這樣做並沒有把直接定義在 fibo 中的函式名稱寫入到當前符號表裡,只是把模組 fibo 的名字寫到了那裡。

可以使用模組名稱來訪問函式:

import fibo

fibo.fib(1000)
fibo.fib2(1000)


from … import 語句

from 語句讓你從模組中匯入一個指定的部分到當前名稱空間中,語法如下:

from modname import name1[, name2[, ... nameN]]

要匯入模組 fibofib 函式,使用如下語句:

from fibo import fib, fib2

fib(1000)
fib2(1000)

from … import * 語句

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

from modname import *

深入模組

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

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

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

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

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

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

from fibo import fib, fib2

fib(1000)
fib2(1000)

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

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

from fibo import *
fib(500)

這將把所有的名字都匯入進來,但是那些由單一下劃線 _ 開頭的名字不在此例。大多數情況, Python 程式設計師不使用這種方法,因為引入的其它來源的命名,很可能覆蓋了已有的定義。

__name__ 屬性

一個模組被另一個程式第一次引入時,其主程式將執行。如果我們想在模組被引入時,模組中的某一程式塊不執行,我們可以用 __name__ 屬性來使該程式塊僅在該模組自身執行時執行。

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

if __name__ == '__main__':
    print('程式自身在執行')
else:
    print('我來自另一模組', __name__)


$ python using_name.py
程式自身在執行

$ python
>>> import using_name
我來自另一模組
>>>

說明: 每個模組都有一個 __name__ 屬性,當其值是 __main__ 時,表明該模組自身在執行,否則是被引入。

說明: __name____main__ 底下是雙下劃線

dir() 函式

內建的函式 dir() 可以找到模組內定義的所有名稱。以一個字串列表的形式返回

import fibo
import sys

d = dir(fibo)
print(d)

d = dir(sys)
print(d)

如果沒有給定引數,那麼 dir() 函式會羅列出當前定義的所有名稱:

a = [1, 2, 3, 4, 5]

import fibo

fib = fibo.fib
print(dir())  # 得到一個當前模組中定義的屬性列表

a = 5  # 建立一個新的變數 'a'
print(dir())

del a  # 刪除變數名a

print(dir())


標準模組

Python 本身帶著一些標準的模組庫

有些模組直接被構建在解析器裡,這些雖然不是一些語言內建的功能,但是他卻能很高效的使用,甚至是系統級呼叫也沒問題。

這些元件會根據不同的作業系統進行不同形式的配置,比如 winreg 這個模組就只會提供給 Windows 系統。

應該注意到這有一個特別的模組 sys ,它內建在每一個 Python 解析器中。變數 sys.ps1sys.ps2 定義了主提示符和副提示符所對應的字串:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Runoob!')
Runoob!
C> 

包是一種管理 Python 模組名稱空間的形式,採用"點模組名稱"

比如一個模組的名稱是 A.B , 那麼他表示一個包 A 中的子模組 B

針對不同的音訊檔案格式(基本上都是透過字尾名區分的,例如: .wav.aiff.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 的檔案才會被認作是一個包

最簡單的情況,放一個空的 __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 是一個不區分大小寫的系統。

在 Windows 平臺上,我們無法確定一個叫做 ECHO.py 的檔案匯入為模組是 echo 還是 Echo ,或者是 ECHO

為了解決這個問題,我們只需要提供一個精確包的索引。

匯入語句遵循如下規則:如果包定義檔案 __init__.py 存在一個叫做 __all__ 的列表變數,那麼在使用 from package import * 的時候就把這個列表中的所有名字作為包內容匯入。

作為包的作者,可別忘了在更新包之後保證 __all__ 也更新了啊。

sounds/effects/__init__.py 中包含如下程式碼:

__all__ = ["echo", "surround", "reverse"]

這表示當你使用 from sound.effects import * 這種用法時,你只會匯入包裡面這三個子模組。

如果 __all__ 真的沒有定義,那麼使用 from sound.effects import * 這種語法的時候,就不會匯入包 sound.effects 裡的任何子模組。他只是把包 sound.effects 和它裡面定義的所有內容匯入進來(可能執行\_\_init\_\_.py裡定義的初始化程式碼)。

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

記住,使用 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 被執行前定義。可以修改這個變數,用來影響包含在包裡面的模組和子包。

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

builtins 內建模組

內建函式

可以直接用,而不需要引入的內建模組

print(__builtins__)     # 全域性變數,等於模組 builtins
for b in dir(builtins):
    print(b)

相關文章