[Python]模塊、包

1. 模塊與import語句

任何Python源文件都能以模塊的形式使用,例如:

# spam.py
a = 37
def foo():
    print("I'm foo and a is %s" % a)
def bar():
    print("I'm bar and I'm calling foo")
class Spam(object):
    def grok(self):
        print("I'm Spam.grok")

要以模塊的形式加載這段代碼,可以使用import spam語句。首次使用import加載模塊時,它將做3件事:
1) 創建新的命名空間,用作在相應源文件中定義的所有對象的容器。
2) 在新創建的命名空間中執行模塊中包含的代碼。
3) 在調用函數中創建名稱爲引用模塊命名空間,這個名稱與模塊的名稱相匹配,例如:

import spam
x = spam.a
spam.foo()
s = spam.Spam()
s.grok()

import執行已加載源的文件中的所有語句。要導入多個模塊,可以爲import提供逗號分隔的模塊名稱列表,例如:

import socket, os, re

用於引用模塊的名稱可以使用as限定符進行更改,例如:

import spam as sp
import socket as net
sp.foo()
sp.bar()
net.gethostname()

import語句可以出現在程序中的任何位置,但是每個模塊中的代碼僅加載和執行一次。
 

2. 從模塊導入選定符號

from語句用於將模塊中的具體定義加載到當前命名空間中。from語句相當於import,但它不會創建一個名稱來引用新創建的模塊命名空間,而是將對模塊中定義的一個或多個對象的引用放到當前命名空間中。例如:

from spam import foo
foo()
spam.foo() # NameError

from語句還會接受用逗號分隔的對象名稱列表,例如:

from spam import(foo, bar, Spam)

另外,as限定符可用於重命名使用from導入的具體對象,例如:

from spam import Spam as Sp
s = Sp()

"*"星號通配符也可用於加載模塊中的所有定義,但以下劃線開頭的定義除外,例如:

from spam import *

form module import 語句只能在模塊最頂層使用。通過定義列表__all__,模塊可以精確控制from spam import \導入的名稱集合,例如:

# spam.py
__all__ = ['bar', 'spam']

使用from導入形式導入定義不會更改定義的作用域規則。函數的全局命名空間始終是定義該函數的模塊,而不是將函數導入並調用該函數的命名空間。
 

3. 模塊搜索路徑

加載模塊時,解釋器在sys.path路徑中搜索字典列表。sys.path中的第一個條目通常是空字符串,表示當前正在使用的字典。sys.path中的其他條目可能包含字典名稱、.zip歸檔文件和.egg文件。各個條目在sys.path中列出的順序決順決定了加載模塊時的搜索順序。
可以將一組模塊打包爲一個文件,設想創建兩個模塊foo.py和bar.py,並將它們放在一個名爲mymodules.zip的zip文件中,就可以以如下方式添加到搜索路徑:

import sys
sys.path.append("mymodules.zip")
import foo, bar

zip文件可以與常規路徑名稱組件混合使用,例如:

sys.path.append("/tmp/modules.zip/lib/python")

從zip中導入需要注意一些限制。首先,只能從歸檔文件中導入.py、.pyw、.pyc和.pyo文件。而且,從歸檔文件加載.py文件時,Python不會建.pyc和.pyo文件,這會導致加載模塊時的性能下降。
 

4. 模塊加載和編譯

使用import加載的模塊實際上可分爲4個通用類別:
1) 使用Python編寫的代碼
2) 已被編譯爲共享庫或DLL的C或C++擴展
3) 包含一組模塊的包
4) 使用C編寫並鏈接到Python解釋器的內置模塊
查看編塊(以foo爲例)時,解釋器在sys.path下的每個目錄中搜索以下文件(按搜索順序列出):
1) 目錄foo,它定義了一個包
2) foo.pyd、foo.so、foomodule.so或foomodule.dll(已編譯的擴展)
3) foo.pyo(只適用於使用了-O或-OO選項時)
4) foo.pyc
5) foo.py(Windows上還會查找.pyw文件)
對於.py文件,首次導入模塊時,它會被編譯爲字節碼並作爲.pyc文件寫回磁盤。在後續導入中,解釋器將加載這段預編譯的字節碼,除非.py文件的修改日期要更新一些。.pyc文件與解釋器的-O選項結合,文件已刪除了行號、斷言和其他調試信息的字節碼。如果指定-OO選項,還會從文件中刪除文檔字符串。
只有使用import語句才能將文件自動編譯爲.pyc和.pyo文件。另外,如果包含模塊的.py文件的目錄不允許寫入,將不會創建這些文件。解釋器的-B選項也可以禁止生成這些文件。
如果存在.pyc和.pyo文件,則可以沒有相應的.py文件。但即使沒有提供源文件,仍然可以檢查並找到大量細節。並且,爲某個Python版本生成的.pyc文件可能不適用於其他的Python版本。
 

5. 包

包可用於將一組模塊分組到一個常見的包名稱下,這有助於解決不同應用程序中使用的模塊名稱之間的命名空間衝突問題。包是通過使用與其相同的名稱創建目錄,並在該目錄中創建文件__init__.py來創建的。可以將如下形式組織一個包:

Graphics/
    __init__.py
    Primitive/
        __init__.py
        lines.py
        fill.py
        text.py
    Graph2d/
        __init__.py
        plot2d.py
    Graph3d/
        __init__.py
        plot3d.py
    png.py
    tiff.py
    jpeg.py

import語句用於通過多種方式從包中加載模塊:
1) import Graphics.Primitive.fill
2) from Graphics.Primitive import fill
3) from Graphics.Primitive.fill import floodfill
只要第一次導入包中的任何部分,就會執行__init__.py中的代碼。這個文件可以爲空。在import語句執行期間,遇到的所有__init__.py文件都會執行。因此,之前示例中的import Graphics.Primitive.fill語句將會首先執行Graphics目錄中的__init__.py文件,然後執行Primitive目錄中的__init__.py文件。
處理以下這條語句時需注意:

from Graphics.Primitive import *

使用該語句希望將與某個包相關聯的所有子模塊導入到當前命名空間中,但是該語句只會導入在Primitive目錄的__init__.py文件中定義的所有名稱。這種行爲可以通過定義列表__all__來修改,例如:

# Graphics/Primitive/__init__.py
__all__ = ["lines", "text", "fill"]

如果想要導入同一個包中的其他子模塊時,可以使用相對導入,例如:

# fill.py
from . import lines

相對導入也可以加載同一個包的不同目錄中包含的子模塊,例如:

# plot2d.py
from ..Primitives import lines

相對導入只能命名用from module import symbol形式的導入語句來指定。因此import ..Primitives.lines或import .lines這樣的語句在語法上是不對的。
最後,Python導入一個包時,它將定義特殊變量__path__,該變量包含一個目錄列表,查找包的子模塊時將搜索這個列表。__path__可通過__init__.py文件中包含的代碼訪問,最初包含的一項具有包的目錄包稱。如果有必要,包可以向__path__列表提供更多目錄,更改查找子模塊時使用的搜索路徑。

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