随着程序越来越复杂,我们需要通过模块来管理代码。Python 中的模块是一个以 .py 结尾的文件。文件名就是模块名,在模块内部可以通过 __name__ 来访问。

在模块内部,模块有一个全局符号表,保存所有模块内的全局变量和函数。我们可以使用 modname.itemname 来访问这些全局变量和函数。模块内的代码,会在模块被导入的时候执行一次,且只会执行一次。

我们可以通过 import 语句导入其他模块,import 语句有很多变种,我们来看几种用法。

>>> from fibo import fib, fib2 # 从 fibo 模块中导入 fib 和 fib2
>>> import fibo # 导入 fibo 模块,之后通过 fibo.fib 的方式使用模块内的函数和变量
>>> from fibo import * # 导入 fibo 模块中所有的变量和函数到当前模块中
>>> import fibo as fib # 导入模块 fibo 并命名为 fib
>>> from fibo import fib as fibonacci # 导入 fibo 模块中的 fib ,并命名为 fibonacci
1
2
3
4
5

当我们导入一个模块的时候,导入的信息会被存放在模块的符号表中。

如果模块被当做脚本来执行,那么模块的 __name__ 值会被设为 __main__,因此,我们可以在模块内通过这个特性来判断当前模块是被当做脚本来执行来时被当做模块是使用的。

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))
1
2
3

此时我们通过 python fibo.py <arguments> 来运行模块,上面的代码会被执行,而通过 import fibo 导入模块时,上面的代码不会被执行。

我们可以通过 dir() 函数来查看模块中定义了哪些变量和函数,比如:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
1
2
3
4
5

dir() 无法查看内置的变量和函数,我们可以通过 builtins 这个标准模块来查看:

>>> import builtins
>>> dir(builtins)
1
2

查找模块

假设我们现在要导入一个名为 spam 的模块。Python 首先会在内置的模块中查找,如果没找到,则会在 sys.path 指定的一堆目录中查找名为 spam.py 的文件。

sys.path 指定的目录包含下面这些:

  • 包含当前脚本的目录
  • PYTHONPATH 环境变量指定的目录(类似于 shell 中的 PATH)
  • 安装环境的默认目录

在初始化完成之后,可以通过代码修改 sys.path,包含当前脚本的目录处在模块查找路径的最前面。也就是说,如果在当前目录中有一个模块与系统标准库模块名称相同,那么当我们导入这个名称的模块时,会先命中当前目录中模块。

标准模块

Python 有很多标准模块,有些与特定的运行平台相关,有些是通用模块。比如 sys 模块就是通用模块。

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
1
2
3
4
5
6
7
8
9
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')  # 修改 Python 查找模块的路径
1
2

在 Python 中,用包来管理模块。A.B 表示使用包 A 中的模块 B

下面的目录结构展示了一个音频处理程序的包结构。

sound/                          # 顶层包
      __init__.py               # 包初始化文件
      formats/                  # 格式转换子包
              __init__.py       # 包初始化文件
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  # 声音处理子包
              __init__.py       # 包初始化文件
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  # 过滤器子包
              __init__.py       # 包初始化文件
              equalizer.py
              vocoder.py
              karaoke.py
              ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

文件 __init__.py 是必须的,Python 通过这个文件来判断当前目录是一个包还是一个目录。__init__.py 可以是一个空文件,也可以做一些初始化工作。

我们可以直接导入包中的子模块,

import sound.effects.echo
1

在使用这个模块的时候,我们必须使用全名,

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
1

当然我们还可以这样导入模块:

from sound.effects import echo

echo.echofilter(input, output, delay=0.7, atten=4)

from sound.effects.echo import echofilter

echofilter(input, output, delay=0.7, atten=4)
1
2
3
4
5
6
7

当使用 from package import item 导入的时候,item 可以是包中的模块、子包,甚至可以是包中定义的变量或函数。当 Python 找不到 item 的时候,会抛出 ImportError 错误。

当我们使用 from package import * 会发生什么呢?如果我们在包的 __init__.py 中定义了 __all__ 变量之后,from package import * 会导入 __all__ 中定义的所有变量。

比如在 sound/effects/__init__.py 中定义了

__all__ = ["echo", "surround", "reverse"]
1

那么,当使用 from sound.effects import * 时,只会导入 echosurroundreverse 三个变量。

如果 __init__.py 中没有定义 __all__ 呢?Python 不会导入所有的子包和模块。Python 会先确保 sound.effects 已经导入,然后导入这个包中定义的任何名字,即 __init__.py 中定义的名字,以及 __init__.py 中导入的任何子模块的名字。

在编程实践中,我们不建议使用 from package import *,因为这样会导致代码的意图不清晰,我们应该使用 from package import specific_submodule 的方式。

包内引用

当一个包内有多个子包的时候,子包之前可能会有相互引用的需求。

我们可以通过绝对路径的方式来引用其他子包。

比如 sound.filters.vocoder 可以通过 from sound.effects import echo 来导入 echo 模块。

也可以通过相对路径的方式来引用其他子包。

比如:

from . import echo
from .. import formats
from ..filters import equalizer
1
2
3

注意,相对路径是相对于当前模块所在的位置。同时,当模块被当做主模块 __main__ 使用时,必须通过绝对路径的形式引用其他模块。

关注微信公众号,获取最新推送~