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__
---------------------------------------------------------------------------
NameError                                 Traceback (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']
---------------------------------------------------------------------------
KeyError                                  Traceback (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 上提的問題:

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM