程式扎記: [ The python tutorial ] 6. Modules

標籤

2012年1月10日 星期二

[ The python tutorial ] 6. Modules

翻譯自 這裡 
Modules : 
之前在 Interpreter 寫的代碼在離開 interpreter 後就沒法重複使用! 別擔心, 你可以將代碼(script)寫到檔案中並重複使用. 而該檔案我們稱為 module. 底下是 Python 對 module 的說明 : 
A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended.

如果你寫的 module 被 main module 引入(import)後, 你可以使用 __name__ 來取得該 module 的名稱. 考慮你撰寫一個 module 如下 : 
- fibo.py :
  1. # Fibonacci numbers module  
  2.   
  3. def fib(n):  # write Fibonacci series up to n  
  4.     a, b = 01  
  5.     while b < n:  
  6.         print(b, end=' ')  
  7.         a, b = b, a+b  
  8.     print()  
  9.   
  10. def fib2(n):  # return Fibonacci series up to n  
  11.     result = []  
  12.     a, b = 01  
  13.     while b < n:  
  14.         result.append(b)  
  15.         a, b = b, a+b  
  16.     return result  

接著你可以在 Interpreter 如下引入該 Module 並使用該 Module 提供的函示 : 
 

More on Modules : 
你寫的 Module 除了函式定義外, 可能包含可以執行的代碼. 這些代碼通常是用來初始化你的 Module 使用, 所以只會在 "第一次" 你 import 這個 module 時才會執行 (第二次, 第三次...的 import 都不會執行這些代碼.). 另外你可能也會在 Module 裡定義變數, 該變數在該 Module 有自己的 symbol table, 所以如果你要 access 這些變數, 你可以使用 modname.itemname 形式去存取. 另外當你使用 import 在當前 Module 去引入其它 Module 時, 該Module 的名稱就會存在當前的命名空間中, 但可能你並不想如此 (可能你已經有同名的變數在當前命名空間), 你可以如下引入某 Module 的某函式定義, 而不將該 Module 的名稱加到當前命名空間 : 
>>> from fibo import fib, fib2 # 只引入 Module "fibo" 的 fib, fib2 函式定義
>>> fibo
Traceback (most recent call last):
File "", line 1, in
NameError: name 'fibo' is not defined
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

另外你如果想一次引入 Module 所有的定義, 則可以參考下面用法 : 
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

上面用法要注意的是所有以 underscore (_) 開頭的變數, 函數 並不會被引入! 另外如果你在 Interpreter 引入過某個 Module 後, 請之後改變該 Module 的內容, 你必須重新啟動 Interpreter 並重新載入該 Module 才會生效. 底下是相關說明 : 
Note For efficiency reasons, each module is only imported once per interpreter session. Therefore, if you change your modules, you must restart the interpreter – or, if it’s just one module you want to test interactively, use imp.reload(), e.g. import impimp.reload(modulename).

- Executing modules as scripts 
如果你想如下面把你的 Module 當作執行檔執行 : 
python fibo.py 

當你這麼使用 Module 時, __name__ 變數會被設為 "__main__", 所以你可以如下在 fibo.py 寫你想要跑的代碼 : 
  1. if __name__ == "__main__":  
  2.     import sys  
  3.     fib(int(sys.argv[1]))  
並如下執行該 Module : 
$ python fibo.py 50
1 1 2 3 5 8 13 21 34

而上面寫的代碼在你用 import 方式引入該 Module 時, 並不會執行. 

- The Module Search Path 
底下為當你 import 某個 Module 時, 尋找路徑的說明 : 
When a module named spam is imported, the interpreter searches for a file named spam.py in the directory containing the input script and then in the list of directories specified by the environment variable PYTHONPATH. This has the same syntax as the shell variable PATH, that is, a list of directory names. WhenPYTHONPATH is not set, or when the file is not found there, the search continues in an installation-dependent default path; on Unix, this is usually.:/usr/local/lib/python.

事實上當你執行後, Module 的搜尋路徑會被放在 sys.path. 參考如下 : 
>>> import sys # 引入 sys 模組
>>> sys.path
['', 'C:\\Windows\\system32\\python32.zip', 'C:\\Software\\Python3.2.2\\DLLs', 'C:\\Software\\Python3.2.2\\lib', 'C:\\Software\\Python3.2\\test1', 'C:\\tmp\\test2', 'C:\\Software\\Python3.2.2\\lib\\site-packages', 'C:\\tmp\\test3', 'C:\\tmp\\test4']

所以這也意味著你可以動態的去修改 Python 搜尋模組的路徑. 另外因為當前路徑會是第一個被尋找, 所以盡量不要取與標準模組同名的 .py 檔, 否則就會發生鬼擋牆 : 
Note that because the directory containing the script being run is on the search path, it is important that the script not have the same name as a standard module, or Python will attempt to load the script as a module when that module is imported. This will generally be an error. See section Standard Modules for more information.

- “Compiled” Python files 
一個 spam.py 的 Module 經過 Python 編譯後會成為 spam.pyc (byte code). 當你的 .py 檔沒有變更時, 為了快速執行的目的, Python 會使用編譯後的 .pyc 當作執行目標. 而 .pyc 當會記錄著 .py 檔的時間標記. 當 .py 有變動, 則 .pyc 會自動被忽略並以 .py 重新進行編譯. 底下是更多相關說明 : 
  • When the Python interpreter is invoked with the -O flag, optimized code is generated and stored in .pyo files. The optimizer currently doesn’t help much; it only removes assert statements. When -O is used, all bytecode is optimized; .pyc files are ignored and .py files are compiled to optimized bytecode.

  • Passing two -O flags to the Python interpreter (-OO) will cause the bytecode compiler to perform optimizations that could in some rare cases result in malfunctioning programs. Currently only __doc__ strings are removed from the bytecode, resulting in more compact .pyo files. Since some programs may rely on having these available, you should only use this option if you know what you’re doing.

  • A program doesn’t run any faster when it is read from a .pyc or .pyo file than when it is read from a .py file; the only thing that’s faster about .pyc or .pyo files is the speed with which they are loaded.

  • When a script is run by giving its name on the command line, the bytecode for the script is never written to a .pyc or .pyo file. Thus, the startup time of a script may be reduced by moving most of its code to a module and having a small bootstrap script that imports that module. It is also possible to name a .pyc or .pyo file directly on the command line.

  • It is possible to have a file called spam.pyc (or spam.pyo when -O is used) without a file spam.py for the same module. This can be used to distribute a library of Python code in a form that is moderately hard to reverse engineer.

  • The module compileall can create .pyc files (or .pyo files when -O is used) for all modules in a directory.

  • Standard Modules : 
    Python 有些內建的 Modules, 有興趣的可以參考 Python Library Reference. 有些是有平台相依性, 如 winreg 模組只能在 Windows like 的 OS 使用, 而有些是專門使用在 Interpreter 中. 而有一個模組 sys 則是非常值得去知道的 (任何平台的 Interpreter 都有內建). 在該模組有定義兩個變數 sys.ps1 與 sys.ps2 分別是 Interpreter 的 Primary prompt 與 Secondary prompt : 
    >>> import sys # 引入模組 sys
    >>> sys.ps1 # Primary prompt
    '>>> '
    >>> sys.ps2
    '... '
    >>> sys.ps1 = 'C> ' # 改變 Primary prompt 為 'C> '
    C> print('John')
    John
    C>

    另外 sys.path 可以知道你的模組搜尋路徑, 在初始的時候會從環境變數 PYTHONPATH 載入設定的路徑. 不過就算已經進入 Interpreter, 你還是可以動態使用串列語法操作它添加模組搜尋路徑 : 
    - 在 Windows console 設定環境變數 PYTHONPATH : 
    >set PYTHONPATH=C:\\ABC

    - 檢視設定與動態添加模組搜尋路徑 
    >>> import sys
    >>> sys.path
    ['', 'C:\\ABC', ...(略)..., 'C:\\tmp\\test4']
    >>> sys.path.append("C:\\123") # 動態添加路徑
    >>> sys.path
    ['', 'C:\\ABC', ...(略)..., 'C:\\123']

    Packages : 
    在某些時候當你開發程式到一定規模, 檔案或是模組很容易發生同名的衝突, 這時你可以使用檔案結構將同名的模組區隔開來解決前面提到的問題. 考慮你有專案目錄分布如下 : 
     

    當你匯入 (引入) 套件時, Python 會到 sys.path 包含的路徑去尋找. 上面的檔案結構你會發現有 __init__.py 存在, 只有目錄包含該檔案, 才會被視為是合法的搜尋套件路徑, 它可以是空的內容也可以包含代碼並將代碼中的變數設定到該套件的 __all__ 的裡. 首先你可以如下匯入套件 echo : 
    import sound.effects.echo

    結著如果你要使用匯入套件的模組, 則必須如下呼叫 : 
    sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) sound.effects.echo 為匯入 套件.模組

    一個較方便的匯入方式如下 : 
    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)

    底下是相關說明 : 
    Note that when using from package import item, the item can be either a submodule (or subpackage) of the package, or some other name defined in the package, like a function, class or variable. The import statement first tests whether the item is defined in the package; if not, it assumes it is a module and attempts to load it. If it fails to find it, an ImportError exception is raised.

    Contrarily, when using syntax like import item.subitem.subsubitemeach item except for the last must be a package; the last item can be a module or a package but can’t be a class or function or variable defined in the previous item.

    - Importing * From a Package 
    接著請考慮句子 from sound.effects import * 的行為, 會引入該套件下所有的模組嗎? 答案是不會. 還記得前面我們有提到一個合法的套件裡必須包含檔案 __init__.py. 而你可以在裡面撰寫代碼並使用 __all__ 變數加入你想要匯入的套件當前面的 from sound.effects import * 被呼叫. 如你可以在 sounds/effects/__init__.py 內加入 : 
    __all__ = ["echo", "surround", "reverse"]

    當執行命令 from sound.effects import * 則會引入上述的三個模組. 除了上面的方法, 你也可以先 import 某個套件下的模組, 接著使用 from <套件> import * 將剛剛在該套件下匯入的模組以當前的名稱空間匯入, 這樣你就可以省略前置的套件名稱 : 
    import sound.effects.echo
    import sound.effects.surround
    from sound.effects import * #匯入模組 echo, surround 到當前名稱空間

    - Intra-package References 
    很方便的是當你在某個模組 (在某個套件下) 如果你想要引用同套件下的其他模組, 可以使用 relative import 語法. 假如你在模組 surround 中 : 
    from . import echo # 引入同套件的模組 echo
    from .. import formats # 引入上層套件 formats
    from ..filters import equalizer #引入上層套件 filters 下的模組 equalizer

    但如果你在的模組是入口模組 (__name__='__main__') , 則匯入功能只能用絕對路徑. 相關說明如下 : 
    Note that relative imports are based on the name of the current module. Since the name of the main module is always "__main__", modules intended for use as the main module of a Python application must always use absolute imports.

    - Packages in Multiple Directories 
    透過底下說明, 你可以拓展某個套件搜尋路徑 : 
    Packages support one more special attribute, __path__. This is initialized to be a list containing the name of the directory holding the package’s __init__.py before the code in that file is executed. This variable can be modified; doing so affects future searches for modules and subpackages contained in the package.While this feature is not often needed, it can be used to extend the set of modules found in a package.

    考慮當前路徑下你有檔案結構如下 : 
    - john\abc.py :
    1. def funName(name):  
    2.     print("abc.py -> {0}".format(name))  

    - johnson\test.py :
    1. def funName(name):  
    2.     print("test.py -> {0}".format(name))  

    接著進入 Interpreter : 
    >>> import john.abc # 匯入套件 john 下的模組 abc
    >>> john.abc
    <module 'john.abc' from 'john\abc.py'>
    >>> john.abc.funName('John')
    abc.py -> John
    >>> import john.test # 嘗試匯入套件 john 下的模組 test
    Traceback (most recent call last):
    File "", line 1, in
    ImportError: No module named test
    >>> john.__path__
    ['john']
    >>> john.__path__.append('johnson') # 動態添加套件 john 的搜尋路徑
    >>> import john.test
    >>> john.test.funName('Ken')
    test.py -> Ken
    >>> john.test
    <module 'john.test' from 'johnson\test.py'>

    Supplement : 
    [Python 學習筆記] 函式、類別與模組 : 模組 (匯入模組) 
    [Python 學習筆記] 函式、類別與模組 : 模組 (匯入套件)

    沒有留言:

    張貼留言

    網誌存檔

    關於我自己

    我的相片
    Where there is a will, there is a way!