python中的import机制

1. module 与 package 与 import

module 是 python 中的一个组织单位,独立构成命名空间,本质上是一个 Python object。在实际应用中,一个 module 常常对应一个 .py 文件。

这里需要注意的是:

  • module 是 python 级别的概念,本身是 python 运行过程中的一类 object,存在于内存之中;
  • 文件是操作系统级别的概念,其存在于硬盘之上。

import 就是将 module 与文件联系起来的过程,其本质是将 .py 文件中定义的内容导入到内存中(同时有可能也会显式地执行一些其他命令),形成 module 供 python 在运行时使用。

package 则是一类特殊的 module,与 module 相比,只是多了一个 __path__ 属性。而对应到操作系统层级,package 一般对应一个目录,而 module 对应其中的一个文件。

目录中可以有子目录,可以有文件,则 package 之中也可以有 subpackagemodule

在 python3 之前,package 对应的目录下必须有 __init__.py 文件
而在 python3 中,__init__.py 文件可有可无,只不过如果有的话,该 package 在被导入时会优先只执行 __init__.py 文件中的内容。

总结:import 就是将 python 程序中的 模块名 / 包名 与操作系统层级的文件/目录进行绑定,并进行命名空间的更新。

2. 项目目录结构

test_import
├── main.ipynb
├── main.py
├── multiprocessing.py
├── my_module.py
└── my_package
    ├── __init__.py
    ├── my_module.py
    ├── my_package.py
    ├── my_subpackage
    │   ├── __init__.py
    │   └── my_subpackage_module.py

3. 绝对引入

1. python 寻找 module 的路径顺序

python 在寻找要 import 的 module 时会在固定的路径下寻找,可以使用 sys.path 来查看此路径。

>>> import sys
>>> print(sys.path)
['~/test_import', 
'~/miniforge3/envs/dlwpt/lib/python310.zip',
'~/miniforge3/envs/dlwpt/lib/python3.10', 
'~/miniforge3/envs/dlwpt/lib/python3.10/lib-dynload', 
'~/miniforge3/envs/dlwpt/lib/python3.10/site-packages']

[!warning] .py 文件与.ipynb 文件
本文章的所有测试均基于.py 文件,但笔者发现,如果是在.ipynb 文件中,运行上述代码,得到的结果恰恰相反。

>>> import sys
>>> print(sys.path)
['~/miniforge3/envs/dlwpt/lib/python310.zip', 
'~/miniforge3/envs/dlwpt/lib/python3.10', 
'~/miniforge3/envs/dlwpt/lib/python3.10/lib-dynload',
'',
'~/miniforge3/envs/dlwpt/lib/python3.10/site-packages']

可以看到,在 .ipynb 文件中运行,得到的环境路径中当前目录排在倒数第二位。

2. 在当前目录下自定义与 built-in module同名的module

python 自带的 module 称为 built-in module

从上一节可以看到 sys.path 所得最前面的就是当前文件所在的目录(有时会显示为 "")。因此如果在当前目录下创建与 built-in module 同名的 .py 文件并导入的话,python 根据 sys.path 中顺序寻找指定 module 时,会率先在当前目录下找到匹配的 .py 并导入,然后就停止搜索后续路径了,因此,此时导入的就是自定义的 module 而非 built-in module。

>>> import multiprocessing
>>> print(multiprocessing)
<module 'multiprocessing' from '~/test_import/multiprocessing.py'>

可以看到这里导入的是当前目录下的multiprocessing.py,而不是build-in模块multiprocessing。

同样,下面这行代码也会报错,因为当前路径下的multiprocessing.py并没有Process类。

>>> from multiprocessing import Process
Traceback (most recent call last):
  File "~/test_import/main.py", line 15, in <module>
    from multiprocessing import Process
ImportError: cannot import name 'Process' from 'multiprocessing' (~/test_import/multiprocessing.py)

[!warning] 如果是 .ipynb 文件
但是需要注意的是,如果本文件是个 .ipynb 文件,那么 sys.path 中路径的顺序会发生变化,build-in 模块的路径会被放在最前面,也就是出现同名时会导入 build-in 模块。

3. 如果使用链式 import 语法的话

如果使用 package.subpackage...module 的方式导入 module,那么所有的package和subpackage都会被导入(这意味着所有package和subpackage中的__init__.py文件都会被执行),但还是只有最后的module会被导入,package和subpackage中的其他mudule并不会被导入。

导入链上所有的 package 和 module 都会被显式导入。

[!note] package 中的 __init__.py 文件
当 package 被导入时,其目录下的 __init__.py 文件会被优先运行一遍。

# my_package/__init__.py
# print(f"my_package is imported")
# my_package/my_subpackage/__init__.py
# print(f"my_subpackage is imported")

>>> import my_package.my_subpackage.my_subpackage_module
my_package is imported
my_subpackage is imported

>>> print(my_package)  # 可以看到my_package变量还存在
<module 'my_package' from '~/test_import/my_package/__init__.py'>

>>> print(my_package.my_subpackage)  # 可以看到my_subpackage变量也还存在
<module 'my_package.my_subpackage' from '~/test_import/my_package/my_subpackage/__init__.py'>

>>> print(my_package.my_subpackage.my_subpackage_module)
<module 'my_package.my_subpackage.my_subpackage_module' from '~/test_import/my_package/my_subpackage/my_subpackage_module.py'>

4. 但是如果使用 as

但是如果使用 from ... import ... as xxx 将最后将导入的module赋值给了其他变量,则只有最后的 module 会被导入,package 和 subpackage 对应的 object 变量会在导入后被清除。

>>> import my_package.my_subpackage.my_subpackage_module as m
my_package is imported
my_subpackage is imported

>>> print(m)  # 但是只有m变量会被保留
<module 'my_package.my_subpackage.my_subpackage_module' from '~/test_import/my_package/my_subpackage/my_subpackage_module.py'>

>>> print(m.__package__)  # 同样,m的__package__属性也会被保留
my_package.my_subpackage

>>> print(my_package)  # 但是此处会报错,因为my_package变量已经被清除
Traceback (most recent call last):
  File "~/test_import/main.py", line 39, in <module>
    print(my_package)
NameError: name 'my_package' is not defined.

# 同理下面两行也会报错
# print(my_package.my_subpackage)
# print(my_package.my_subpackage.my_subpackage_module)

5. 如果 module 与其 package 同名

可以看到输出的路径是包 (也就是对应的 __init__.py 文件) 而不是模块,也就是说如果出现 package 与其中的 module 同名,那么导入的是 package 而不是 module。

>>> import my_package.my_package
my_package is imported
>>> print(my_package)
<module 'my_package' from '~/test_import/my_package/__init__.py'>

如果想要导入 module,可以使用 as 语法:

>>> import my_package.my_package as m
>>> print(m)
<module 'my_package.my_package' from '~/test_import/my_package/my_package.py'>

可以看到此时输出的是module而不是package。

你可能感兴趣的:(python,开发语言)