獲取本機中python模組相關資訊
不同於 C++、Java、C# namespace 僅作為符號隔離字首,Python 模組是執行期物件。模組對應同名原始碼檔案,為成員提供全域性名字空間。
模組物件
模組物件有幾個重要屬性:
- name: 模組名 .,在 sys.modules 中以此為主鍵。
- file: 模組完整檔名。
- dict: 模組 globals 名字空間。
除使用 py 檔案外,還可動態建立模組物件。
>>> import sys, types
>>> m = types.ModuleType("sample", "sample module." ) # 用 type 建立物件。
>>> m
<module 'sample' (built-in)>
>>> m.__dict__
{'__name__': 'sample', '__doc__': 'sample module.'}
>>> "sample" in sys.modules # 並沒有新增到 sys.modules。
False
>>> def test(): print "test..."
>>> m.test = test # 動態新增模組成員。
>>> m.test()
test...
為模組動態新增函式成員時,須注意函式所引用的是其定義模組的名字空間。
>>> def test(): print "test:", __name__
>>> test()
test: __main__
>>> m.test = test
>>> m.test()
test: __main__
imp.new_module() 也可用來動態建立模組物件,同樣不會新增到 sys.modules。
>>> import imp
>> > m = imp.new_module("test")
>>> m
<module 'test' (built-in)>
>>> m.__dict__
{'__name__': 'test', '__doc__': None, '__package__': None}
reload
當模組原始檔發生變更時,可使用內建函式 reload() 重新匯入模組。新建模組物件依舊使用原記憶體地址,只是原先被引用的內部成員物件不會被同步重新整理。
測試一下,為避免本地名字引用造成干擾,我們直接從 sys.modules 獲取模組。
>>> import sys
>>> hex(id(sys.modules["string"]))
'0x10b4fc6e0'
>>> reload(sys.modules["string"])
<module 'string'>
>>> hex(id(sys.modules["string"])) # reload 後的模組地址未曾改變,所以其他地方對
'0x10b4fc6e0' # 該模組的引用就不會失效,且被 "重新整理"。
如果改用手動方法重新載入,那麼就會出現兩個不同的模組物件了。
>>> del sys.modules["string"]
>>> sys.modules["string"] = __import__("string")
>>> hex(id(sys.modules["string"])) # 地址變了。
'0x10bc17a98'
搜尋路徑
虛擬機器按以下順序搜尋模組 (包):
- 當前程序根目錄。
- PYTHONPATH 環境變數指定的路徑列表。
- Python 標準庫目錄列表。 -路徑檔案 (.pth) 儲存的目錄 (通常放在 site-packages 目錄下)。
程序啟動後,所有這些路徑都被組織到 sys.path 列表中 (順序可能會被修改)。任何 import 操作都按照 sys.path 列表查詢目標模組。當然,可以用程式碼往 sys.path 新增自定義路徑。
虛擬機器按以下順序匹配目標模組:
- py 原始碼檔案。
- pyc 位元組碼檔案。
- egg 包檔案或目錄。
- so、dll、pyd 等擴充套件檔案。
- 內建模組。
- 其他。
要執行程式,原始檔不是必須的。實際上,很多軟體釋出時都會刪掉 py 檔案,僅保留二進位制 pyc 位元組碼檔案。但要注意,位元組碼很容易被反編譯,不能奢求它能帶來安全。
find_module
可用 imp.find_module() 獲取模組的具體檔案資訊。
>>> import imp
>>> imp.find_module("os")
(
<open file '/System/.../2.7/lib/python2.7/os.py', mode 'U' at 0x1013aa420>,
'/System/.../2.7/lib/python2.7/os.py',
('.py', 'U', 1)
)
匯入模組
程序中的模組物件通常是唯一的。在首次成功匯入後,模組物件被新增到 sys.modules,以後匯入操作總是先檢查模組物件是否已經存在。可用 sys.modules[name] 獲取當前模組物件。
關鍵字 import 將包、模組或成員物件匯入到當前名字空間中,可以是 globals,也可以是函式內部的 locals 名字空間。
>>> import pymongo, redis
>>> import pymongo.connection, pymongo.database
>>> import pymongo.connection as mgoconn, pymongo.database as mgodb
>>> from pymongo import connection
>>> from pymongo import connection, database
>>> from pymongo import connection as mgoconn, database as mgodb
>>> from pymongo import *
>>> from pymongo.connection import *
如果待匯入物件和當前名字空間中已有名字衝突,可用 as 更換別名。需要注意,"import *" 不會匯入模組私有成員 (以下劃線開頭的名字) 和 all 列表中未指定的物件。
在函式中使用 "import *" 會引發警告,雖然不影響使用,但應該避免引入用不到的名字。(Python 3 已經禁止該用法了)
def main():
import test
from test import add, _x
from sys import * # SyntaxWarning: import * only allowed at module level
all
因為 import 實際匯入的是目標模組 globals 名字空間中的成員,那麼就有一個問題:目標模組也會匯入其他模組,這些模組同樣在目標模組的名字空間中。"import *" 操作時,所有這些一併被帶入到當前模組中,造成一定程度的汙染。建議在模組中用 all 指定可被批量匯出的成員名單。
__all__ = ["add", "x"]
私有成員和 all 都不會影響顯式匯出目標模組成員。Python 並沒有嚴格的私有許可權控制,僅以特定的命名規則來提醒呼叫人員。
__import__
和 import 關鍵字不同,內建函式 import() 以字串為引數匯入模組。匯入的模組會被新增到 sys.modules,但不會在當前名字空間中建立引用。
>>> import sys
>>> sys.modules.get("zlib") # 沒有 zlib。
>>> __import__("zlib") # 匯入 zlib,返回模組物件。
<module 'zlib'>
>>> sys.modules.get("zlib") # zlib 新增到 sys.modules。
<module 'zlib'>
>>> "zlib" in globals() # 名字空間中沒有 zlib,除非將 __import__ 結果關聯到某個名字。
False
用 import 匯入 package.module 時,返回的是 package 而非 module。看下面的例子:
test <dir>
|_ __init__.py
|_ add.py
>>> m = __import__("test.add")
>>> m # 返回的並不是 test.add 模組。
<module 'test' from 'test/__init__.pyc'>
>>> m.__dict__.keys() # 還好 add 在 test 的名字空間中。
['__file__', ..., '__path__', 'add']
>>> m.add # 得這樣才能訪問 add 模組。
<module 'test.add' from 'test/add.pyc'>
只有 fromlist 引數不為空時,才會返回目標模組。
>>> m = __import__("test.add", fromlist = ["*"])
>>> m
<module 'test.add' from 'test/add.pyc'>
>>> m.__dict__.keys()
['__builtins__', '__file__', '__package__', 'hi', 'x', '__name__', '__doc__']
import 太麻煩,建議用 importlib.import_module() 代替。
>>> import sys, importlib
>>> m = importlib.import_module("test.add")
>>> m # 返回的是目標模組,而非 package。
<module 'test.add' from 'test/add.pyc'>
>>> sys.modules.get("test.add") # 模組自然要新增到 sys.modules。
<module 'test.add' from 'test/add.pyc'>
>>> "test.add" in globals() # 沒有新增到當前名字空間中。
False
>>> importlib.import_module(".add", "test") # 使用 "." 或 ".." 指定模組在多層
<module 'test.add' from 'test/add.pyc'> # package 中位置。(必須)
注意:關鍵字 import 總是優先查詢當前模組所在目錄,而 import、import_module 則是優先查詢程序根目錄。所以用 import、import_module 匯入包模組時,必須帶上包字首。
load_source
imp 另提供了 load_source()、load_compiled() 等幾個函式,可用來載入不在 sys.path 搜尋路徑列表中的模組檔案。優先使用已編譯的位元組碼檔案,模組物件會被新增到 sys.modules。
需要小心,這些函式類似 reload(),每次都會新建模組物件。
>>> imp.load_source("add", "./test/add.py")
<module 'add' from './test/add.pyc'>
構建包
將多個模組檔案放到獨立目錄,並提供初始化檔案 init.py,就形成了包 (package)。
無論是匯入包,還是匯入包中任何模組或成員,都會執行初始化檔案,且僅執行一次。可用來初始化包環境,儲存幫助、版本等資訊。
all
"from import *" 僅匯入 init.py 的名字空間,而該檔案通常又只是個空檔案,這意味著沒有任何模組被匯入。此時就需要用 all 指定可以被匯入的模組名字列表,該定義無需將模組顯式引入到 init.py 名字空間。
$ cat test/__init__.py
__all__ = ["add"]
有太多理由不建議使用 "import *",比如引入不需要的模組,意外 "覆蓋" 當前空間同名物件等等。
換種做法,將要公開的模組和模組成員顯式匯入到 init.py 名字空間中,呼叫者只需 "import ",然後用 "." 就可訪問所需的目標物件。如此可規避上述問題,還有助於隱藏包的實現細節,減少外部對包檔案組織結構的依賴。
path
某些時候,包內的檔案太多,需要分類存放到多個目錄中,但又不想拆分成新的包或子包。這麼做是允許的,只要在 init.py 中用 path 指定所有子目錄的全路徑即可 (子目錄可放在包外)。
test <dir>
|_ __init__.py
|
|_ a <dir>
. |_ add.py
|
|_ b <dir>
|_ sub.py
$ cat test/__init__.py
__path__ = ["/home/yuhen/py/test/a", "/home/yuhen/py/test/b"]
稍微改進一下。還可以用 os.listdir() 掃描全部子目錄,自動形成路徑列表。
from os.path import abspath, join
subdirs = lambda *dirs: [abspath(join(__path__[0], sub)) for sub in dirs]
path = subdirs(“a”, “b”)
pkgutil
如果要獲取包裡面的所有模組列表,不應該用 os.listdir(),而是 pkgutil 模組。
test <dir>
|_ __init__.py
|_ add.py
|_ user.py
|
|_ a <dir>
. |_ __init__.py
. |_ sub.py
|
|_ b <dir>
|_ __init__.py
|_ sub.py
>>> import pkgutil, test
>>> for _, name, ispkg in pkgutil.iter_modules(test.__path__, test.__name__ + "."):
... print "name: {0:12}, is_sub_package: {1}".format(name, ispkg)
...
name: test.a , is_sub_package: True
name: test.add , is_sub_package: False
name: test.b , is_sub_package: True
name: test.user , is_sub_package: False
>>> for _, name, ispkg in pkgutil.walk_packages(test.__path__, test.__name__ + "."):
print "name: {0:12}, is_sub_package: {1}".format(name, ispkg)
...
name: test.a , is_sub_package: True
name: test.a.sub , is_sub_package: False
name: test.add , is_sub_package: False
name: test.b , is_sub_package: True
name: test.b.sub , is_sub_package: False
name: test.user , is_sub_package: False
函式 iter_modules() 和 walk_packages() 的區別在於:後者會迭代所有深度的子包。
pkgutil.get_data() 可讀取包內任何檔案內容。
>>> pkgutil.get_data("test", "add.py")
'#coding=utf-8\n\nx = 1\n\ndef hi():\n pass\n\n\nprint "add init"\n'
egg
將包壓縮成單個檔案,以便於分發和安裝。類似 Java JAR 那樣。
- 安裝 setuptools。
$ sudo easy_install setuptools
-
建立空目錄,將包目錄完整拷貝到該目錄下。
- 建立 setup.py 檔案。(http://docs.python.org/2/distutils/setupscript.html)
from setuptools import setup, find_packages
setup (
name = "test",
version = "0.0.9",
keywords = ("test", ),
description = "test package",
url = "http://github.com/qyuhen",
author = 'Q.yuhen',
author_email = "[email protected]",
packages = find_packages(),
)
- 建立 egg 壓縮檔案。
$ python setup.py bdist_egg
running bdist_egg
running egg_info
creating test.egg-info
... ...
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist/test-0.0.9-py2.7.egg' and adding 'build/.../egg' to it
removing 'build/bdist.macosx-10.8-intel/egg' (and everything under it)
生成的 egg 檔案存放在 dist 目錄。
$ tar tvf dist/test-0.0.9-py2.7.egg
-rwxrwxrwx 0 0 0 1 12 30 00:40 EGG-INFO/dependency_links.txt
-rwxrwxrwx 0 0 0 226 12 30 00:40 EGG-INFO/PKG-INFO
-rwxrwxrwx 0 0 0 228 12 30 00:40 EGG-INFO/SOURCES.txt
-rwxrwxrwx 0 0 0 5 12 30 00:40 EGG-INFO/top_level.txt
-rwxrwxrwx 0 0 0 1 12 30 00:40 EGG-INFO/zip-safe
-rwxrwxrwx 0 0 0 21 12 30 00:15 test/__init__.py
-rwxrwxrwx 0 0 0 137 12 30 00:40 test/__init__.pyc
-rwxrwxrwx 0 0 0 60 12 30 00:15 test/add.py
-rwxrwxrwx 0 0 0 305 12 30 00:40 test/add.pyc
-rwxrwxrwx 0 0 0 0 12 30 00:15 test/user.py
-rwxrwxrwx 0 0 0 133 12 30 00:40 test/user.pyc
-rwxrwxrwx 0 0 0 0 12 30 00:15 test/a/__init__.py
-rwxrwxrwx 0 0 0 139 12 30 00:40 test/a/__init__.pyc
-rwxrwxrwx 0 0 0 8 12 30 00:15 test/a/sub.py
-rwxrwxrwx 0 0 0 151 12 30 00:40 test/a/sub.pyc
-rwxrwxrwx 0 0 0 0 12 30 00:15 test/b/__init__.py
-rwxrwxrwx 0 0 0 139 12 30 00:40 test/b/__init__.pyc
-rwxrwxrwx 0 0 0 8 12 30 00:15 test/b/sub.py
-rwxrwxrwx 0 0 0 151 12 30 00:40 test/b/sub.pyc
將 test-0.0.9-py2.7.egg 全路徑新增到路徑檔案 (.pth) 或 PYTHONPATH 環境變數就可使用。更最常見的做法是將其安裝到 site_packages 目錄。
$ sudo easy_install dist/test-0.0.9-py2.7.egg
Processing test-0.0.9-py2.7.egg
Copying test-0.0.9-py2.7.egg to /Library/Python/2.7/site-packages
Adding test 0.0.9 to easy-install.pth file
Installed /Library/Python/2.7/site-packages/test-0.0.9-py2.7.egg
Processing dependencies for test==0.0.9
Finished processing dependencies for test==0.0.9
安裝後的搜尋路徑被自動新增到 site-packages/easy-install.pth 檔案。
轉載:
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/module.html