Python 除了在解释器中实时执行,或者单个脚本执行,在复杂程序中也有必要对代码进行组织封装,这就是 Python 的模块文件,与模块相对的,称导入并使用模块的脚本为主文件。

模块例子

一个模块的简单例子如下,由一个 demo.py 文件组成,此时 demo 就是模块名。模块的内容包括其中定义的函数以及可执行的语句等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# demo.py

def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()

def fib2(n):
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result

# print("hi, this is module demo")
s = 1

在解释器或者其它脚本中,使用如下语句可以导入模块名

1
import demo

然后就可以通过 demo.sdemo.fib 等访问模块中的变量和函数。接下来的内容以这个模块为例。

查找模块路径

解释器通过以下路径查找模块:

  • 输入脚本的目录(或未指定文件时的当前目录)
  • PYTHONPATH 环境变量(目录列表,与 shell 变量 PATH 的语法一样)。
  • 依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。

可以使用如下语句打印当前状态的所有的查找路径

1
2
import sys
print(sys.path)

导入模块语句

导入模块的语法有很多种:

  1. 只导入模块名称 import demo,此时对于模块内的标识符,都可以加模块名来访问,例如 demo.s
  2. 导入单个标识符 from demo import s,此时可以直接使用模块中的 s,但是模块名和其它的标识符是无法访问的;类似地,可以导入多个标识符 from demo import fib,fib2
  3. 导入所有标识符 from demo import *,此时可以直接使用模块中的所有标识符。

注意:

  • 标识符的导入存在冲突风险,如果不同模块中存在同名的标识符,并且被完全导入,那么后导入的会覆盖先导入的,导致未知错误。因此不建议全部导入,按需导入即可。
  • 导入标识符建议使用别名,既可以规避重名冲突,也可以简化标识符,例如对整个模块使用别名 import numpy as np,或者导入单个标识符时使用别名 from numpy import linspace as ls
  • 在导入所有标识符的语句 from demo import * 中,并不会导入任何下划线开头的标识符,这些被视作私有的,不会自动导入。

可以使用如下命令查看模块中定义的标识符信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dir(demo)

'''
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'fib',
'fib2']
'''

注意解释器通常不会重复导入同一个模块,如果修改了模块,建议重启解释器会话。

避免执行模块

在导入模块时,会依次执行模块的所有语句,例如前文中的 demo.py 文件代表的 demo 模块,导入时会自动执行一次赋值语句 s=1

通常,我们并不希望导入模块时伴随模块语句的执行,而是仅仅在当前文件作为主文件时才执行,可以使用如下语句完成这样的效果,这里我们参考 C++的习惯,也定义一个 main() 函数

1
2
3
4
5
6
def main():
print("hi, this is module demo")
s = 1

if __name__=="__main__":
main()

此时,我们通过解释器导入模块,不会显示欢迎信息,但是在命令行直接执行这个脚本,就会显示欢迎信息

1
2
$ python demo.py
hi, this is module demo

模块与包

模块往往对应的是一个单独的 py 文件,与之相对的,包则对应了一个文件夹结构,在其中含有 __init__.py 文件,以及其它的很多 py 文件,这些文件均为一个个单独的模块,称为一个包的子模块。

Python 查找包的时候需要在路径中找到对应包的 __init__.py 文件,将含有这个文件的文件夹视作一个包,例如如下的文件结构

1
2
3
4
5
6
7
Demo
|-A
a1.py
a2.py
|-B
b1.py
__init__.py

对应的是一个名为 Demo 的包,含有 Demo.A.a1 等三个子模块。

在 pip 或者 conda 进行的下载就是以包为单位的。