Python 模块完全限定名,模块缓存介绍

我被代码海扁署名-非商业-禁演绎
阅读 7:56·字数 2384·发布 
Bilibili 空间
关注 960

本节所讲述的内容,并不完全适用于通过类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的完全限定名是其自身。

teacher.py
print('teacher 模块!')
student.py
# 运行 student.py 可以正常导入 teacher,因为 student.py 所在的目录将被添加至搜索路径
import teacher
Windows
python student.py
teacher 模块!
UNIX/Linux/macOS
python3 student.py
teacher 模块!

调整模块student,为其导入同一目录的包homework,在该包对应的__init__.py文件中,我们导入了包中的模块english。这里请注意模块english的完全限定名homework.english,他基于student.py所在的位置。

student.py
# …
# homework 包也可以被正常导入
import homework
homework/__init__.py
print('homework 包!')

# 这里的完全限定名基于 student.py 所在的目录 import homework.english
homework/english.py
print('english 模块!')
Windows
python student.py
teacher 模块!
homework 包!
english 模块!
UNIX/Linux/macOS
python3 student.py
teacher 模块!
homework 包!
english 模块!

对于完全限定名homework.english,如果我们直接执行homework包的__init__.py文件(示例中命令行的当前目录为__init__.py所在的文件夹,当然,你可以切换到其他位置),那么将产生一个错误。因为模块搜索路径已经不再包含student.py所在的目录,完全限定名homework.english应改为english

Windows
python __init__.py
homework 包!

ModuleNotFoundError: No module named 'homework'
UNIX/Linux/macOS
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会被抛出。

car.py
# 一个表示汽车的类
class Car:
	pass
cache_none.py
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对象的类型,会发现他们并不相同。

cache_clear.py
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