python模块导入详解

目录

包和模块

模块导入

绝对导入

sys.modules

sys.meta_path

sys.path

相对导入

__init__.py相对导入

mian脚本中的相对导入

Reference


包和模块

        我们可以在一个脚本中定义变量、函数和类,并添加逻辑,但是当某个项目具有复杂的逻辑时,我们希望把这些逻辑分解成不同的脚本,并以一定的合理的方式组织成层级结构,这种具有层级结构的包含脚本的文件夹就称为包。

        模块通常指的是定义了变量、函数和类的脚本。但是实际上,包也属于模块的一种,区分一个模块到底是包还是某个具体的脚本,就看该模块是否具有__path__属性,具有__path__属性的模块称为包,没有__path__属性的模块就是我们最长见到的常规脚本。

        在python3.3之前,要组成一个包,还必须在顶层目录开始,每一层结构目录中,都必须包含__init__.py文件,但是在python3.3开始,可以没有__init__.py文件。上述中,当我们说包是模块时,我们说的这个包指的就是其目录下的__init__.py初始化文件,这一点,可以通过在_init__.py中打印出__name__属性验证,即__init__.py文件的__name__属性就是对应的包名。

模块导入

        当导入一个模块时,该模块会被执行,当模块是一个包时,根据上述,该包实际上就是该包下的__init__.py文件,所以导入包时,该包对应的__init__.py文件会被执行。当进行类似import A.B.C或者from A.B import C的导入时,会依次执行A、B下的初始化文件,当然如果C依然是一个子包的话,C下的初始化文件也会被执行。

        这里需要强调一下import语句和from xxx import yyy语句的一点重要区别,import语句后面跟随的必须是模块,但是from xxx import yyy语句中,yyy不一定是模块,也可以是变量、函数或者类。

        模块导入的方式分为绝对导入和相对导入。

绝对导入

        在python3开始,绝对导入指的就是import A或者from A import B这种语句。那么,当python在执行import A语句或者from A import B语句时,背后到底做了哪些事情呢?即python到底时如何去查找对应的模块的呢?

sys.modules

        首先,由于启动python解释器时,python会自己预导入一些内置和标准模块,并把这些被导入的模块记录在sys.modules中,因此,当导入一个模块时,python会先到sys.modules中查找,如果可以找到,那么直接提取对应的模块,如果找不到,则继续下一步查找。

sys.meta_path

        接下来,python会直接按照sys.meta_path中各个查找器的顺序去查找模块,sys.meta_path是一个包含了几个模块查找器的列表,默认情况下,sys.meta_path中具有如下的查找器,以及查找顺序:

[, , ]

        可以看到,第一个查找器时内置模块查找器,即python如果没有在sys.modules中找到相应模块,则会进一步查找内置模块中是否有想要被导入的模块。如果内置模块中没有找到,会进一步查找frozen模块,如果还是没有找到,则再通过PathFinder去查找。这里最后一个PathFinder实际上就是通过sys.path中的路径逐个查找的,如果还是没有找到,则最终会报出异常。

        sys.meta_path是一个可以改变的列表,如果我们在导入模块之前,改变这个列表的内容,都会影响python对模块的查找,比如调换顺序则会影响python对模块的查找顺序,或者直接清空该列表,那么python除了sys.modules缓存的模块外,不能成功导入任何模块。

        内置模块指的是用c编写的关键组件模块,可以通过sys.builtin_module_names查看有哪些内置模块;frozen模块则是用python编写,但是通过python的freeze工具编译后同python解释器一同发布的模块,这种模块可以直接再unix系统上运行,就跟c的二进制程序一样。

        如果我们想知道某个模块的详细信息,可以通过importlib.util.find_spec(m)函数获取模块m的信息,可以查看其到底时什么模块。

sys.path

        可以看到,在进入sys.path中的路径查找之前,python对于模块查找会先经历sys.modules缓存查找、内置模块查找和frozen模块查找三个环节,只有上面三个都没有找到,才会进入sys.path路径查找。

        sys.path中主要依次包含三类路径:直接主脚本所在的目录或者进行交互python环境所在的工作目录、PYTHONPATH路径和包默认的安装路径(如site-package)。

相对导入

        除了绝对导入外,python中还可以进行包内的相对导入,用点号开头表示,比如from . import xxx、from .. import xxx、from .A import xxx、from ..A import xxx等都是相对导入,相对导入有两个语法特点:1、 一定时以from开头导入;2、from后跟的名称一定是以点号开头,表示相对路径。

        相对导入是通过当前模块的名称__name__来定位被导入模块位置的。假设包A的结构如下所示,在test.py中有from .. import t0,t1的__name__属性为A.B.t1,一个点号表示A.B,两个点号表示A,python会通过t1的__name__属性来定位其所在的位置,以及查找被导入模块的位置。

A/
  __init__.py
  t0.py
  B/
    __init__.py
    test.py
    t1.py

        那么,当执行from .. import t0时,python背后到底做了什么?首先,跟绝对导入一样,python会在模块缓存sys.modules中查找是否已经有A,如果没有,则会先导入A,进一步检查是否有A.t0,如果有就直接引用,如果没有,python会先从A的命名空间中查找,A的命名空间就是A下面的__init__.py脚本执行后,里面所创建的对象,如果没有在A的命名空间中找到,则会在A对应目录下查找,如果还是没有找到,则会报出异常。所以,对于相对导入,在真正查找到物理模块前,会先经历sys.modules缓存模块查找和包模块的初始化文件创建的命名空间查找。

__init__.py相对导入

        __init__.py初始化文件中的相对导入和常规相对导入是一样的,这里之所以特地拿出来强调,是因为在目录查找之前,还会经历__init__.py的命名空间查找,所以如果所在相应的包目录下并不存在想要被导入的模块,但是在__init__.py中定义了相应的被导入对象的话,那么导入语句依然会成功运行,会导入__init__.py中定义的对象。

mian脚本中的相对导入

       windows系统下,python3在main脚本中不允许相对导入。

        在unix系统下,通常相对导入语法只会在包内发生,但是有一个特例,那就是在__name__属性为__main__的主脚本中也允许相对导入语法。当在主脚本中运行相对导入时,主脚本就相当于包的顶层目录下的__init__.py,只允许一个点号,即from . import x,因为如果超过一个点号就会报超出顶层包目录的异常,而且,这里的x也只有在主脚本里面在导入语句之前被定义的情况下,该相对导入语句才会成功运行。

Reference

1. 5. The import system — Python 3.10.4 documentation     

2. Python Cookbook, Third Edition: Beazley, David, Jones, Brian

3. PEP 328 – Imports: Multi-Line and Absolute/Relative | peps.python.org

你可能感兴趣的:(python编程,python,模块导入)