如果已知如何创建和执行程序(或脚本),以及如何使用 import
将函数从外部模块导入到程序中。
那么如何编写自己的模块呢?
import math
math.sin(math.pi / 2)
1.0
下面来具体看一下操作过程。
任何Python程序都可作为模块导入。假设编写了以下代码所示的程序,
并将其保存在文件 hello.py
中,这个文件的名称(不包括扩展名 .py
)将成为模块的名称。
# hello.py
print("Hello, world!")
Hello, world!
with open('xx_hello.py' ,'w') as fo:
fo.write('print("Hello, world!")')
这里假设这个文件存储在目录 C:\python
(Windows)或 ~/python
(UNIX/macOS)中。
要告诉解释器去哪里查找这个模块,可执行如下命令(以Windows目录为例):
import os
pwd = os.getcwd()
print(pwd)
/home/jovyan/work/jupylab_houxue/pt01_basic/ch10_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
创建的对象,
需要重新创建它。
模块在首次被导入程序时执行。这看似有点用,但用处不大。让模块值得被创建的原因在于它们像类一样,有自己的作用域。这意味着在模块中定义的类和函数以及对其进行赋值的变量都将成为模块的属性。这看似复杂,但实际上非常简单。
在模块中定义函数:
假设编写了一个类似于以下代码所示的模块,并将其存储在文件 hello2.py
中。
另外, 假设将这个文件放在了Python解释器能够找到的地方。
提示:像处理模块那样,让程序可用后,可使用Python 解释器开关 -m
来执行它。
如果随其他模块一起安装了文件 progname.py
, 即导入了 progname
,
命令 python -m progname args
将使用命令行参数 args
来执行程序 progname
。
# hello2.py
'''
只包含一个函数的简单模块
'''
def hello():
print("Hello, world!")
with open('xx_hello2.py', 'w') as fo:
fo.write('''
def hello():
print("Hello, world!")
''')
现在可以像下面这样导入它:
import hello2
这将执行这个模块,也就是在这个模块的作用域内定义函数 hello
,因此可像下面这样访问这个函数:
hello2.hello()
Hello, world!
将代码放在模块中,就可在多个程序中使用它们。因此,要让代码是可重用的,务必将其模块化!
模块用于定义函数和类等,但是通常情况下,添加一些测试代码来检查情况是否符合预期很有用。
例如,如果要确认函数 hello
管用,可能将模块 hello2
重写为如下所示的模块 hello3
。
如下是一个简单的模块,其中的测试代码有问题。
# 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
语句中,如以下代码所示:
一个包含有条件地执行的测试代码的模块:
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!
注意:如果要编写更详尽的测试代码,将其放在一个独立的程序中可能是个不错的主意。