module
是 python 中的一个组织单位,独立构成命名空间,本质上是一个 Python object
。在实际应用中,一个 module
常常对应一个 .py
文件。
这里需要注意的是:
module
是 python 级别的概念,本身是 python 运行过程中的一类 object,存在于内存之中;- 而文件是操作系统级别的概念,其存在于硬盘之上。
而 import
就是将 module
与文件联系起来的过程,其本质是将 .py
文件中定义的内容导入到内存中(同时有可能也会显式地执行一些其他命令),形成 module
供 python 在运行时使用。
而 package
则是一类特殊的 module
,与 module
相比,只是多了一个 __path__
属性。而对应到操作系统层级,package
一般对应一个目录,而 module
对应其中的一个文件。
目录中可以有子目录,可以有文件,则 package
之中也可以有 subpackage
和 module
。
在 python3 之前,
package
对应的目录下必须有__init__.py
文件
而在 python3 中,__init__.py
文件可有可无,只不过如果有的话,该package
在被导入时会优先且只执行__init__.py
文件中的内容。
总结:import
就是将 python 程序中的 模块名 / 包名 与操作系统层级的文件/目录进行绑定,并进行命名空间的更新。
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
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 文件中运行,得到的环境路径中当前目录排在倒数第二位。
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 模块。
如果使用 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'>
但是如果使用 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)
可以看到输出的路径是包 (也就是对应的 __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。