Python 模組的載入順序
基本概念
module
模組, 一個 py 檔案或以其他檔案形式存在的可被匯入的就是一個模組
package
包,包含有 init 檔案的資料夾
relative path
相對路徑,相對於某個目錄的路徑
absolute path
絕對路徑,全路徑
Python 直譯器是如何查詢包和模組的
Python 執行一個 py 檔案,無論執行的方式是用絕對路徑還是相對路徑,interpreter 都會把檔案所在的 directory 加入 sys.path 這個 list 中,並且是索引為 0 的位置。Python 就是在 sys.path 中查詢包和模組的。
# test.py # coding:utf-8 import sys print(sys.path) print('Now in main.py') def hello(): print('michael hello') if __name__ == '__main__': hello() # 執行 python test.py $ python test.py ['/tmp/module-package/app', '/usr/lib64/python27.zip', '/usr/lib64/python2.7', '/usr/lib64/python2.7/plat-linux2', '/usr/lib64/python2.7/lib-tk', '/usr/lib64/python2.7/lib-old', '/usr/lib64/python2.7/lib-dynload', '/usr/lib64/python2.7/site-packages', '/usr/lib/python2.7/site-packages'] Now in test.py michael hello
Python 直譯器查詢包的順序
直譯器查詢包:
- 直譯器會預設載入一些 modules,除了
sys.builtin_module_names
列出的內建模組之外,還會載入其他一些標準庫,都存放在sys.modules
字典中。 - 然後就是搜尋
sys.path
路徑下的模組了。
In [3]: import sys In [4]: print(sys.builtin_module_names) ('_abc', '_ast', '_codecs', '_collections', '_functools', '_imp', '_io', '_locale', '_operator', '_signal', '_sre', '_stat', '_string', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'atexit', 'builtins', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'posix', 'pwd', 'sys', 'time', 'xxsubtype', 'zipimport')
這樣的查詢順序將會導致同名包或模組被遮蔽。
示例2:
# tree $ tree . -L 1 . ├── __init__.py ├── name ├── os.py ├── test2.py ├── test.py └── test.pyc # test2.py import os from redis import Redis from test import hello print('Now in test2.py') print(os.getcwd()) # 執行 python test2.py $ python test2.py Traceback (most recent call last): File "test2.py", line 2, in <module> from redis import Redis ImportError: No module named redis
這裡的 os
模組並不是是 built-in module
,上面已經將 sys.builtin_module_names
內容打印出來了。只是 Python 直譯器啟動時就載入到了 sys.modules
中快取起來了。所以,即使在同目錄下有同名模組,直譯器依然是可以找到正確的 os
模組的!如果你在 import os
之前,先執行 del sys.modules['os']
,那麼,標準模組 os
就會被同目錄下的 os.py
遮蔽了。
redis
屬於第三方模組,預設安裝位置是 Python 環境變數中的 site-packages
,直譯器啟動之後,會將此目錄加到 sys.path
,由於當前目錄會在 sys.path
的首位,當前目錄的 redis 優先被找到了, site-packages
中的 redis
模組被遮蔽了。
綜上所述,搜尋的一個順序是: sys.modules
快取 -> sys.path[0]
即當前目錄查詢 -> sys.path[1:]
路徑查詢。
同時發現,模組被載入的時候,其中非函式或類的語句,例如 print('hello')
、 name=michael
等,是會在 import
的時候,預設就執行了。
互動式執行環境的查詢順序
互動執行環境,直譯器會自動把當前目錄加入到 sys.path
,這一點和直接執行檔案是一樣的,但是這種方式下, sys.path[0]
是儲存的當前目錄的相對路徑,而不是絕對路徑。
In [4]: import sys In [5]: sys.path[0] Out[5]: ''
模組中的 __file__
變數
檔案中的 __file__
當模組以檔案的形式出現 file 指的是模組檔案的路徑名,以相對路徑執行 file 是相對路徑,以絕對路徑執行 file 是絕對路徑:
# test3.py print __file__ # 執行 python test.py $ python test3.py test3.py $ python /tmp/module-package/app/test3.py /tmp/module-package/app/test3.py
互動式 Shell 中的 __file__
前互動式 Shell 的執行並不是以檔案的形式載入,所以不存在 __file__
這樣的屬性:
In [8]: __file__ --------------------------------------------------------------------------- NameErrorTraceback (most recent call last) <ipython-input-8-358d5687b810> in <module>() ----> 1 __file__ NameError: name '__file__' is not defined
sys.argv[0]
變數
sys.argv[0]
是獲得入口執行檔案路徑, __file__
是真實被執行模組的檔案路徑。比如下面例子中, test2.py
就是入口執行檔案,而 test.py
就是在 import
時真實被執行的模組
# test.py print(__file__) print(sys.argv[0]) # test2.py import test # 執行 python test2.py /tmp/module-package/app/test.py # __file__ test2.py # sys.argv[0]
sys.modules
的作用
載入的模組存放在何處? 答案是 sys.modules
。 模組一經載入, Python 會把這個模組加入 sys.modules
中供下次載入使用,這樣可以加速模組引入,起到快取作用。 sys.modules
是一個 dict
型別的值。
In [14]: sys.modules['requests'] --------------------------------------------------------------------------- KeyErrorTraceback (most recent call last) <ipython-input-14-8aefaef0aed5> in <module>() ----> 1 sys.modules['requests'] KeyError: 'requests' In [15]: import requests In [16]: sys.modules['requests'] Out[16]: <module 'requests' from '/usr/lib/python2.7/site-packages/requests/__init__.pyc'>
# 沒有預先引入 math,但是 sys.modules 中已經有這個鍵 In [18]: sys.modules['math'] Out[18]: <module 'math' from '/usr/lib64/python2.7/lib-dynload/math.so'>
需要注意的是, sys.modules['math']
儘管可以看到 math
鍵,但是,要使用它,還是需要顯示 import math
之後才能使用的,因為那只是 Python 直譯器後臺快取的,你不顯示引入,本地空間還是不會去發現它。
總結
Python 通過查詢 sys.path
來決定包的匯入,Python直譯器啟動時載入的模組快取 > 同級目錄 > sys.path[1:]
。Python 中的特有屬性 __file__
以及 sys.argv[0]
、 sys.argv[0]
、 sys.modules
可以幫助分析包的查詢和匯入過程。
解決這個問題,請教了大牛同事,果然一下子讓我明白了。於是,自問自答了在 SegmentFault 上提的問題:
- ofollow,noindex">Python 包的引入順序到底是怎樣的?
參考
- 三月沙-如何理解 Python 的模組查詢原理與方式 本文內容主要參考,但是該文章中提到的
os
屬於built-in moulde
的理解是有誤的,本文中修正了理解。 - 構建一個模組的層級包
- The Python Standard Library
- Medium-Python 的 Import 陷阱
- CSDN-Python 模組搜尋路徑 提交了
PYTHONPATH
這個環境變數的作用 - librarybook-The sys module
- 官宣-System-specific parameters and functions