>>> from env_helper import info; info()
页面更新时间: 2024-01-20 22:45:33
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-17-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

10.1. 模块‌

你已知道如何创建和执行程序(或脚本),还知道如何使用import将函数从外部模块导入到程序中。

>>> import math
>>> math.sin(math.pi / 2)
1.0

下面来看看如何编写自己的模块。

10.1.1. 模块就是程序

任何Python程序都可作为模块导入。假设你编写了以下代码所示的程序, 并将其保存在文件 hello.py 中,这个文件的名称(不包括扩展名.py)将成为模块的名称。

>>> # hello.py
>>> print("Hello, world!")
Hello, world!

这里假设这个文件存储在目录 C:\python(Windows)或 ~/python(UNIX/macOS)中。 要告诉解释器去哪里查找这个模块,可执行如下命令(以Windows目录为例):

>>> import os
>>> pwd = os.getcwd()
>>> print(pwd)
/home/bk/book-jubook/python/jubook_python/pt01_basic/ch24_module
 >>> import sys
 >>>
 >>> sys.path.append(pwd)

**提示** 在UNIX中,不能直接将相对路径字符串 ``'~/python'``
附加到sys.path末尾,而必须使用完整的路径(如
``'/home/yourusername/python'`` )。
如果你要自动创建完整的路径,可使用
``sys.path.expanduser('~/python')`` 。

这告诉解释器,除了通常将查找的位置外,还应到当前路径中去查找这个模块。这样做后,就可以导入这个模块了。

>>> import hello
Hello, world!

注意 当你导入模块时,可能发现其所在目录中除源代码文件外,还新建了一个名为__pycache__ 的子目录。这个目录包含处理后的文件,Python能够更高效地处理它们。以后再导入这个模块时,如果.py文件未发生变化, Python将导入处理后的文件,否则将重新生成处理后的文件。删除目录 pycache不会有任何害处,因为必要时会重新创建它。

如你所见,导入这个模块时,执行了其中的代码。但如果再次导入它,什么事情都不会发生。

>>> import hello

这次为何没有执行代码呢?因为模块并不是用来执行操作的,而是用于定义变量、函数、类等。鉴于定义只需做一次,因此导入模块多次和导入一次的效果相同。

为何只导入一次

在大多数情况下,只导入一次是重要的优化,且在下述特殊情况下显得尤为重要:两个模块彼此导入对方。

在很多情况下,你可能编写两个这样的模块:需要彼此访问对方的函数和类才能正确地 发挥作用。例如,你可能创建了两个模块clientdb和billing,分别包含客户数据库和记账系 统的代码。客户数据库可能包含对记账系统的调用(如每月自动向客户发送账单),而记账系统可能需要访问客户数据库的功能才能正确地完成记账。

在这里,如果每个模块都可导入多次,就会出现问题。模块clientdb导入billing,而 billing又导入clientdb,结果可想而知:最终将形成无穷的导入循环。然而,由于第二次导入时什么都不会发生,这种循环被打破。

如果一定要重新加载模块,可使用模块importlib中的函数reload,它接受一个参数,并返回重新加载的模块。 如果在程序运行时修改了模块,并希望这种修改 反映到程序中,这将很有用。要重新加载前述简单的模块 hello , 可像下面这样做:

>>> import importlib
>>> hello = importlib.reload(hello)
Hello, world!

这里假设hello已导入。通过将函数reload的结果赋给hello,用重新加载的版本替换了以前的版本。由于打印出了问候语,说明这里确实导入了这个模块。

通过实例化模块bar中的类Foo创建对象x后,如果重新加载模块bar,并不会重新创建x指 向的对象,即x依然是旧版Foo的对象。要让x指向基于重新加载的模块中的 Foo创建的对象,需要重新创建它。

10.1.2. 模块是用来下定义的

模块在首次被导入程序时执行。这看似有点用,但用处不大。让模块值得被创建的原因在于它们像类一样,有自己的作用域。这意味着在模块中定义的类和函数以及对其进行赋值的变量都将成为模块的属性。这看似复杂,但实际上非常简单。

1.在模块中定义函数

假设你编写了一个类似于代码清单10-2所示的模块,并将其存储在文件hello2.py中。另外, 假设你将这个文件放在了Python解释器能够找到的地方。

提示 像处理模块那样,让程序可用后,可使用Python 解释器开关-m来执行它。如果随其他模块一起安装了文件progname.py, 即导入了progname,命令python -m progname args将使用命令行参数args来执行程序 progname。

>>> # hello2.py
>>>
>>> '''
>>> 只包含一个函数的简单模块
>>> '''
>>> def hello():
>>>     print("Hello, world!")

现在可以像下面这样导入它:

>>> import hello2

这将执行这个模块,也就是在这个模块的作用域内定义函数hello,因此可像下面这样访问 这个函数:

>>> hello2.hello()
Hello, world!

将代码放在模块中,就可在多个程序中使用它们。因此,要让代码是可重用的,务必将其模块化!

10.1.3. 在模块中添加测试代码

模块用于定义函数和类等,但是通常情况下,添加一些测试代码来检查情况是否符合预期很有用。例如,如果要确认函数hello管用,你可能将模块hello2重写为代码清单10-3所示的模块hello3。

代码清单10-3 一个简单的模块,其中的测试代码有问题

>>> # hello3.py
>>> def hello():
>>>     print("Hello, world!")

一个测试:

>>> hello()
Hello, world!

这看似合理:如果将这个模块作为普通程序运行,将发现它运行正常。然而,如果在另一个 程序中将其作为模块导入,以便能够使用函数hello,也将执行测试代码,就像本章的第一个hello 模块一样。

>>> import hello3

hello3.hello()

Hello, world!

这不是你想要的结果。要避免这种行为,关键是检查模块是作为程序运行还是被导入另一个程序。为此,需要使用变量__name__

>>> __name__
'__main__'
>>> hello3.__name__
'hello3'

如你所见,在主程序中(包括解释器的交互式提示符),变量 name 的值是’ main ’,而 在导入的模块中,这个变量被设置为该模块的名称。因此,要让模块中测试代码的行为更合理, 可将其放在一条if语句中,如代码清单10-4所示。

代码清单 一个包含有条件地执行的测试代码的模块

>>> def hello():
>>>     print("Hello, world!")
>>>
>>> def test():
>>>     hello()
>>>
>>> if __name__ == '__main__':
>>>     test()
Hello, world!

如果将这个模块作为程序运行,将执行函数hello;如果导入它,其行为将像普通模块一样。

>>> import hello4
>>> hello4.hello()
Hello, world!

如你所见,我将测试代码放在了函数test中。原本可以将这些代码直接放在if语句中,但通过将其放在一个独立的测试函数中,可在程序中导入模块并对其进行测试。

>>> hello4.test()
Hello, world!

注意 如果要编写更详尽的测试代码,将其放在一个独立的程序中可能是个不错的主意。。