2019python面试题-模块加载顺序
Python 解释器是如何查找包和模块的
Python 执行一个 py 文件,无论执行的方式是用绝对路径还是相对路径,interpreter 都会把文件所在的 directory 加入 sys.path 这个 list 中,并且是索引为 0 的位置。Python 就是在 sys.path 中查找包和模块的。
sys.modules
的作用
载入的模块存放在何处? 答案是 sys.modules
。 模块一经载入, Python 会把这个模块加入 sys.modules
中供下次载入使用,这样可以加速模块引入,起到缓存作用。sys.modules
是一个 dict
类型的值。python中的全局module集合sys.modules被称为modules缓存,保证了module的唯一性,每当有import操作都会在该sys.modules查找,如果不存在就会将该module加入到sys.modules中。
解释器查找包:
- 解释器会默认加载一些 modules,除了
sys.builtin_module_names
列出的内置模块之外,还会加载其他一些标准库,都存放在sys.modules
字典中。sys.modules 包含了所有加载的模块。 import 语句在实际从磁盘上加载模块之前,会先去检查这个字典!*注:内置模块不是标准库,是指sys.builtin_module_names
输出的字符串元组。解释器启动时确实会加载buit-in module
,但是,执行print(sys.builtin_module_names)
发现,os
并不是built-in module
。所以,解释器执行 python 文件时,不仅仅是加载了内置模块的!!!使用sys.modules
可以看到实际启动时加载的所有模块。 - 然后就是搜索
sys.path
路径下的模块了。
import sys 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')
这样的查找顺序将会导致同名包或模块被遮蔽(如下):
# 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
模块的(sys.modules中的os模块)!如果你在import os
之前,先执行del sys.modules['os']
,那么,标准模块 os
就会被同目录下的 os.py
屏蔽了。
*注:del删除模块只是把模块从当前命名空间中删除,但该module依然存在于module缓存中。
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
的时候,默认就执行了。