python3測試工具開發快速入門教程6模塊

模塊

退出Python解釋器再重新進入,函數和變量等的定義都會丟失。 較大的程序使用文本編輯器編輯的文件作爲執行輸入更好,即創建腳本。 程序變很長時可將它分割成幾個更易於維護的文件。你也可能想在幾個程序中使用相同的函數,而不是拷貝代碼。

Python可在文件中定義,在腳本或者解釋器中使用。這樣的文件即模塊。模塊中的定義可導入到其他模塊或主模塊中(即腳本頂層或命令行可以訪問的變量集)。

模塊是包含Python定義和語句的文件。文件名就是模塊名加上.py 綴。模塊名可以由全局變量name獲取。實例:fibo.py:

# Fibonacci numbers module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

命令行導入:

>>> import 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

深入模塊

除了函數定義,模塊也可以包含可執行語句。這些語句一般用來初始化模塊。它們僅在第一次導入時執行。要強行加載,可以使用reload(modulename)。

每個模塊都有自己私有的符號表,模塊內所有的函數將其作爲全局符號表使用。 因此模塊內部使用全局變量不會和用戶的全局變量發生衝突。通過modname.itemname可以引用模塊的全局變量。

模塊中可以導入其他的模塊。 建議導入放在頭部。

導入的另外一種形式:不導入模塊名,直接導入函數、類、變量等。

>>> 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, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

還可以導入所有非私有的定義:

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

這樣可以導入所有非下劃線開頭的命名。因爲可讀性不好,一般不推薦使用。但是可以用不過來保存交付式會話。
需要注意的是在實踐中往往不鼓勵從一個模塊或包中使用 * 導入所有,因爲這樣會讓代碼變得很難讀。不過在交互式會話中很方便。

用腳本的方式執行模塊

執行方式:

python3 fibo.py <arguments>

此時name 設置爲"main",這樣根據是否是主文件判斷執行。比如在fibo.py添加內容:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

這樣導入的時候不會執行上述內容,但是作爲主文件執行時會執行。

$ python3 fibo.py 50
1 1 2 3 5 8 13 21 34
>>> import fibo
>>>

模塊的搜索路徑

導入模塊時,解釋器先在內置模塊中尋找,然後是sys.path。sys.path定義的位置如下:

  • 當前目錄

  • PYTHONPATH變量(類似shell的PATH)

  • Python默認的安裝目錄

初始化之後可以修改sys.path。注意因爲當前的目錄的優先級較高,儘量不要存在和其他模塊名字相同的文件。這是個常見錯誤。

編譯的:Python 文件

pyc文件可以加快大量引用標準模塊的短程序的加載速度法。它會被視爲源碼的預“編譯”( byte-compiled,二進制編譯)版本。python的字節碼文件後綴pyc,以修改時間做爲版本號,如果文件沒有修改,會直接調用pyc文件而不是py文件,反之調用py文件並生成pyc文件,pyc文件如果生成失敗也沒有關係。PHP需要每次都編譯,所以python的效率會明顯比PHP高。

通常你不需要爲創建 spam.pyc 文件做任何工作。一旦 spam.py 成功編譯,就會嘗試生成對應版本的 spam.pyc 。如果有任何原因導致寫入不成功,生成的 spam.pyc 文件就會視爲無效,隨後即被忽略。 spam.pyc 文件的內容是平臺獨立的,所以 Python 模塊目錄可以在不同架構的機器之間共享。

部分高級技巧:

以 -O 參數調用Python 解釋器時會生成優化代碼並保存在.pyo文件中。目前只是刪除了assert語句。pyo文件的優先級高過pyc。
-OO比-O更深一層,刪除了文檔字符串。因爲某些程序依賴於這些變量的可用性,但個別情況下會導致程序不能執行。
.pyc和.pyo只能提高加載速度,不能提高執行速度。
指定文件名在命令行執行時,不會生成.pyc或.pyo文件。所以import動作放入專門的導入模塊更好。當然也可以手工創建.pyc 或 .pyo 文件。
可以只發布.pyc或.pyo文件而不發佈py文件,加大逆向工程的難度。
compileall 模塊爲指定目錄中的所有模塊創建 .pyc 文件(或者使用-O參數創建 .pyo 文件)。

標準模塊

Python的文檔參見標準模塊庫(Python Library Reference)。一些標準模塊庫模塊爲了效率或者訪問系統原語(比如系統調用)內置於解釋器之中,但是並不是python的核心部分,注意有些模塊可能不是跨平臺的。例winreg 模塊在 Windows系統上纔有。有一個具體的模塊值得注意: sys的變量 sys.ps1 和 sys.ps2 定義了主提示符和輔助提示符字符串:

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

這兩個變量只在交互模式下有意義。

變量sys.path是解釋器模塊搜索路徑。它由環境變量 PYTHONPATH 初始化,如果沒有設定PYTHONPATH ,就由內置的默認值初始化。你可以用標準的列表操作修改它:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

dir()函數

內置函數 dir() 用於查看模塊的定義,它返回一個排序的字符串列表:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)  
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__package__',
 '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache',
 '_current_frames', '_getframe', '_mercurial', 'api_version', 'argv',
 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats',
 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_clear', 'exc_info',
 'exc_traceback', 'exc_type', 'exc_value', 'excepthook', 'exec_prefix',
 'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
 'getrefcount', 'getsizeof', 'gettotalrefcount', 'gettrace', 'hexversion',
 'long_info', 'maxint', 'maxsize', 'maxunicode', 'meta_path', 'modules',
 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
 'py3kwarning', 'setcheckinterval', 'setdlopenflags', 'setprofile',
 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'subversion',
 'version', 'version_info', 'warnoptions']

無參數調用時, dir()函數返回當前定義名字列表:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', '__package__', 'a', 'fib', 'fibo', 'sys']

注意該列表列出了所有類型的名稱:變量,模塊,函數等。 dir() 不會列出內置函數和變量名。如果你想列出這些內容,查看builtin 就好:

>>> import __builtin__
>>> dir(__builtin__)  
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError',
 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning',
 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True',
 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning',
 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__',
 '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring',
 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright',
 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval',
 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset',
 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input',
 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license',
 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next',
 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit',
 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round',
 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

參考資料

包是使用“圓點模塊名”格式結構化python模塊命名空間的方法,例如A.B表示A模塊中包含了B子模塊。這樣可以避免多個模塊之間的命名衝突。

假設你現在想要設計一個模塊集(包)來統一處理聲音文件和聲音數據。存在幾種不同的聲音格式(通常由它們的擴展名來標識,例如:.wav, .aiff, .au)。爲了處理不同類型的文件格式之間轉換,你需要維護不斷增長的模塊集合。還要對聲音數據做很多不同的操作(例如混音,添加回聲,均衡,藝術效果),所以需要大量模塊,結構如下:

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for 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.echofilter(input, output, delay=0.7, atten=4)

類似的可以直接導入函數或者變量:

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

注意from package import item中的item既可是包中的子模塊(包),也可以是包中定義的其它名字,像函數、類或變量。import先檢查包中是否有,如果沒有會當做模塊來加載,再找不到它,會引發ImportError 異常。

相反import item.subitem.subsubitem中每個子項必須是包,最後的子項可以是包或模塊,但不能是類、函數或變量等。

import *

看上去from sound.effects import *會導入所有子模塊,花掉很長時間。不過包中定義__all__列表可以避免全部導入。比如effects目錄的init.py文件如果定義了__all__, 上述命令就只會導入該列表對應的子模塊。

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

如果沒有定義__all__, from sound.effects import * 語句不會從sound.effects 包中導入所有的子模塊,它只保證導入了sound.effects,執行了_init__.py:

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

import * 不是建議的寫法。推薦from Package import specific_submodule,但是注意不要有重名。

包內引用

絕對路徑:
 from sound.effects import echo
相對路徑:
    from . import echo
    from .. import formats
    from ..filters import equalizer

需要注意所有導入都基於當前模塊的命名,通常爲 "__main__",主模塊應該總是用絕對路徑。

多目錄包

包的特殊屬性__path__指定了包中包含了init.py文件的目錄,方便擴展,但是很少使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章