Python 模块完全限定名,模块缓存介绍
本节所讲述的内容,并不完全适用于通过类ModuleType
动态创建的模块类型对象。
Python 模块的完全限定名
Python 模块的完全限定名,可以让模块被导入系统正确的搜索到,因此,完全限定名一般需要根据 Python 的模块搜索路径来确定。
当模块对应的py
文件或pyc
字节码文件,或包对应的文件夹,位于模块搜索路径的任意目录中时,模块或包的名称就是其完全限定名。否则,完全限定名应使用.
拼接模块或包所在的父级包的名称,这一过程将重复,直至某个父级包位于模块搜索路径的任意目录。
Python 模块的完全限定名类似于文件系统中的路径
在完全限定名中包含父级包的名称,是合情合理的,因为 Python 包具有命名空间的效果。而大部分情况下,Python 包与文件夹对应,因此,完全限定名的表现方式类似于文件系统的路径。
Python 模块的完全限定名可能会发生变化
当 Python 的模块搜索路径改变时,模块的完全限定名可能会发生相应的变化。当然,应对这种情况,最好的方式是重新调整模块搜索路径,而不是修改代码中的完全限定名。
模块搜索路径
要获取关于模块搜索路径的信息,你可以查看Python 模块搜索路径介绍,Python 模块搜索路径中的目录有哪些一节。
这里,模块teacher
和模块student
位于同一目录,student
通过import
语句导入了teacher
。打开命令行运行student.py
将是可行的(示例中命令行的当前目录为student.py
所在的文件夹,当然,你可以切换到其他位置),因为student.py
所在的目录会被添加至模块搜索路径,模块teacher
的完全限定名是其自身。
print('teacher 模块!')
# 运行 student.py 可以正常导入 teacher,因为 student.py 所在的目录将被添加至搜索路径
import teacher
python student.py
teacher 模块!
python3 student.py
teacher 模块!
调整模块student
,为其导入同一目录的包homework
,在该包对应的__init__.py
文件中,我们导入了包中的模块english
。这里请注意模块english
的完全限定名homework.english
,他基于student.py
所在的位置。
# …
# homework 包也可以被正常导入
import homework
print('homework 包!')
# 这里的完全限定名基于 student.py 所在的目录
import homework.english
print('english 模块!')
python student.py
teacher 模块!
homework 包!
english 模块!
python3 student.py
teacher 模块!
homework 包!
english 模块!
对于完全限定名homework.english
,如果我们直接执行homework
包的__init__.py
文件(示例中命令行的当前目录为__init__.py
所在的文件夹,当然,你可以切换到其他位置),那么将产生一个错误。因为模块搜索路径已经不再包含student.py
所在的目录,完全限定名homework.english
应改为english
。
python __init__.py
homework 包!
…
ModuleNotFoundError: No module named 'homework'
python3 __init__.py
homework 包!
…
ModuleNotFoundError: No module named 'homework'
Python 模块缓存
无论是哪种缓存,使用他们的目的在于提高运行效率,以牺牲部分存储空间为代价,避免大量重复和耗时的工作。Python 的模块缓存包含了已经载入的模块或包,他们存储在sys
模块的modules
变量中,该变量是一个字典,字典中的键对应了模块的完全限定名,字典中的值对应了表示模块的ModuleType
对象。
Python 中的 ModuleType 对象
你可以将ModuleType
对象视为模块在代码层面的真实实现,一般情况下他们保存在sys.modules
字典中。开发人员可以编写代码,来主动创建一个ModuleType
对象并将其加入模块缓存。
sys.modules 中的 None 会导致无法导入相关 Python 模块或包
如果将字典sys.modules
中某个键值对的值设置为None
,那么尝试导入该键值对对应的模块会引发异常ModuleNotFoundError
。对于在设置None
之前导入的模块,则可以延续其有效性。
导入系统
想要获取更多关于导入系统的信息,你可以查看什么是 Python 导入系统一节。
这里模块car
定义了类Car
,在脚本文件cache_none.py
中,我们提前将模块car
对应的缓存设置为None
,然后使用import
语句将其导入,异常ModuleNotFoundError
会被抛出。
# 一个表示汽车的类
class Car:
pass
import sys
# 模块 car 的完全限定名为 car,这里提前将其缓存设置为 None
sys.modules['car'] = None
# 导入模块 car 将导致异常
import car
ModuleNotFoundError: import of car halted; None in sys.modules
清除 Python 模块缓存
虽然没有必要,但你可以清空 Python 的模块缓存,这将导致下一次的导入操作需要重新载入相关的模块或包。
重新导入的 Python 模块与之前导入的 Python 模块不同
如果一个模块对应的缓存被清除,那么重新导入的模块与之前导入的模块,是两个不同的ModuleType
对象,模块中定义的类也会有所区别。
在下面的示例中,我们将模块car
的缓存保存至变量module1
,并创建Car
的实例car1
,之后清除模块的缓存重新导入,创建Car
的实例car2
,对比前后的模块缓存和Car
对象的类型,会发现他们并不相同。
import sys
# 首次导入模块 car,并创建 Car 对象
import car
module1 = sys.modules['car']
car1 = car.Car()
# 清除模块 car 的缓存
del sys.modules['car']
# 再次导入模块 car,并创建 Car 对象
import car
module2 = sys.modules['car']
car2 = car.Car()
print(f'module1 == module2 能成立?{module1 == module2}')
print(f'car1 与 car2 的类型相同?{type(car1) == type(car2)}')
module1 == module2 能成立?False
car1 与 car2 的类型相同?False
源码
teacher.py·codebeatme/python-reference·GitHub
student.py·codebeatme/python-reference·GitHub
homework/__init__.py·codebeatme/python-reference·GitHub
homework/english.py·codebeatme/python-reference·GitHub
car.py·codebeatme/python-reference·GitHub
cache_none.py·codebeatme/python-reference·GitHub
cache_clear.py·codebeatme/python-reference·GitHub