Python学习笔记——6.模块
Python 除了在解释器中实时执行,或者单个脚本执行,在复杂程序中也有必要对代码进行组织封装,这就是 Python 的模块文件,与模块相对的,称导入并使用模块的脚本为主文件。
模块例子
一个模块的简单例子如下,由一个 demo.py
文件组成,此时
demo
就是模块名。模块的内容包括其中定义的函数以及可执行的语句等。
1 | # demo.py |
在解释器或者其它脚本中,使用如下语句可以导入模块名
1 | import demo |
然后就可以通过 demo.s
和 demo.fib
等访问模块中的变量和函数。接下来的内容以这个模块为例。
查找模块路径
解释器通过以下路径查找模块:
- 输入脚本的目录(或未指定文件时的当前目录)
- PYTHONPATH 环境变量(目录列表,与 shell 变量 PATH 的语法一样)。
- 依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。
可以使用如下语句打印当前状态的所有的查找路径
1 | import sys |
导入模块语句
导入模块的语法有很多种:
- 只导入模块名称
import demo
,此时对于模块内的标识符,都可以加模块名来访问,例如demo.s
。 - 导入单个标识符
from demo import s
,此时可以直接使用模块中的s
,但是模块名和其它的标识符是无法访问的;类似地,可以导入多个标识符from demo import fib,fib2
。 - 导入所有标识符
from demo import *
,此时可以直接使用模块中的所有标识符。 - 允许模块包含子模块,此时导入子模块的语句为
import demo.detail
,注意不是from demo import detail
。
注意:
- 标识符的导入存在冲突风险,如果不同模块中存在同名的标识符,并且被完全导入,那么后导入的会覆盖先导入的,导致未知错误。因此不建议全部导入,按需导入即可。
- 导入标识符建议使用别名,既可以规避重名冲突,也可以简化标识符,例如对整个模块使用别名
import numpy as np
,或者导入单个标识符时使用别名from numpy import linspace as ls
。 - 在导入所有标识符的语句
from demo import *
中,并不会导入任何下划线开头的标识符,这些被视作私有的,不会自动导入。
在默认情况下,一个ipynb文件不能像一个py脚本一样被直接导入和使用,可以通过安装import-ipynb
这个工具来支持对ipynb文件的导入
1
pip install import-ipynb
可以使用如下命令查看模块中定义的标识符信息
1 | dir(demo) |
注意:解释器通常不会重复导入同一个模块,如果在导入之后修改模块,在jupyter notebook中建议重启内核来确保修改生效。
如果将一个简单的py脚本导入,python通常会自动在导入脚本所在目录生成__pycache__/
目录,并且在其中存放.pyc
文件,
这是对应模块生成的字节码文件,被缓存用于优化模块的导入速度。(通常会将这些目录和缓存文件从版本管理中排除)
事实上,我们也可以手动控制这个过程,例如将python脚本转换为字节码文件并执行
1
2
3
4
5# 将hello.py编译为hello.xxx.pyc,存放在__pycache__目录下
python -m py_compile hello.py
# 执行pyc文件,效果和执行hello.py一样
python ./__pycache__/hello.cpython-312.pyc
除了
.pyc
文件,还有一个可能见到的.pyd
文件,这是 Python 的动态模块,本质就是 Windows 平台的动态链接库(DLL),它们通常用 C 或 C++ 直接编写,可以被 Python 直接导入使用。
避免执行模块
在导入模块时,会依次执行模块的所有语句,例如前文中的
demo.py
文件代表的 demo
模块,导入时会自动执行一次赋值语句 s=1
。
通常,我们并不希望导入模块时伴随模块语句的执行,而是仅仅在当前文件作为主文件时才执行,可以使用如下语句完成这样的效果,这里我们参考
C++的习惯,也定义一个 main()
函数
1 | def main(): |
此时,我们通过解释器导入模块,不会显示欢迎信息,但是在命令行直接执行这个脚本,就会显示欢迎信息
1 | python demo.py |
模块与包
模块往往对应的是一个单独的 py
文件,与之相对的,包则对应了一个文件夹结构,在其中含有
__init__.py
文件,以及其它的很多 py
文件,这些文件均为一个个单独的模块,称为一个包的子模块。
Python 查找包的时候需要在路径中找到对应包的 __init__.py
文件,将含有这个文件的文件夹视作一个包,例如如下的文件结构
1 | Demo |
对应的是一个名为 Demo 的包,含有 Demo.A.a1
等三个子模块。
在 pip 或者 conda 进行的下载就是以包为单位的。
补充
在VSCode上写Python时,如果涉及到将自己写的py脚本作为模块使用import
导入,很可能会出现这种情况:脚本可以正常执行,但是代码提示疯狂标红。
为了解决这种影响编程体验的问题,可以在.vscode/settings.json
中添加脚本所在的路径,例如
1
2
3
4
5
6{
"python.analysis.extraPaths": [
"./path1/",
"./path2/"
]
}