URLhttps://learnscript.net/zh/python-reference/modules/
    复制链接转到说明  示例

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

    我被代码海扁署名-非商业-禁演绎
    阅读 7:51·字数 2358·发布 

    本节所讲述的内容,并不完全适用于通过类ModuleType动态创建的模块类型对象。

    Python 模块的完全限定名

    Python 模块的完全限定名,可以让模块被导入系统正确的搜索到,因此,完全限定名一般需要根据模块搜索路径来确定。

    当模块对应的py文件或pyc字节码文件,或包对应的文件夹,位于模块搜索路径的任意目录中时,模块或包的名称就是其完全限定名。否则,完全限定名应使用.拼接模块或包所在的父级包的名称,这一过程将重复,直至某个父级包位于模块搜索路径的任意目录。

    模块的完全限定名类似于文件系统中的路径

    在完全限定名中包含父级包的名称,是合情合理的,因为 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对象。

    ModuleType 对象

    你可以将ModuleType对象视为模块在代码层面的真实实现,一般情况下他们保存在sys.modules字典中。开发人员可以编写代码,来主动创建一个ModuleType对象并将其加入模块缓存。

    sys.modules 中的 None 会导致无法导入相关模块或包

    如果将字典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 模块缓存

    虽然没有必要,但你可以清空模块缓存,这将导致下一次的导入操作需要重新载入相关的模块或包。

    重新导入的模块与之前导入的模块不同

    如果一个模块对应的缓存被清除,那么重新导入的模块与之前导入的模块,是两个不同的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