2.4 函数与模块
函数和模块是Python可扩展性的一个重要组成部分。函数可以帮助我们复用代码,减少代码量;模块可以让我们使用已有的现成代码,避免重复工作。
2.4.1 函数
在计算机科学中,函数(Function)是程序负责完成某项特定任务的代码单元,具有一定的独立性。函数通常通过名称进行调用,调用时用一个小括号接受参数,并得到返回值,例如:
c = max(a, b)
1. 函数的定义
在Python中,函数在定义时需要满足这样的规则:
● 使用关键字def引导;
● def后面是函数的名称,括号中是函数的参数,不同的参数用逗号“,”隔开,参数可以为空,但括号不能省略;
● 函数的代码块要缩进;
● 用一对"""包含的字符串作为函数的说明,用来解释函数的用途,可省略,在查看函数帮助时会显示;
● 使用关键字return返回一个特定的值,如果省略,返回None。
例如,我们这样定义一个加法函数,它接受两个变量x和y,计算x与y的和a,并返回a的值:
2. 函数的调用
定义好函数后,函数并不会自动执行。我们需要调用它才能执行相关的内容。
函数的调用使用函数名加括号的形式,参数放在括号中:
Python不限定函数参数的输入类型,因此,我们可以使用不同类型的对象作为参数,只要这些传入的参数支持相关的操作。
对于add()函数,由于数字和字符串都支持加法操作,因此上面的调用都是合法的。
当传入的两个参数不支持加法时,Python会将抛出异常:
当传入的参数数目与实际不符时,也会抛出异常:
传入参数时,Python提供了两种模式,第一种是按照参数的顺序传入,另一种是使用键-值模式,按照参数名称传入参数。
例如,使用键-值模式,上面的例子可以写成:
两种模式可以混用:
In [9]: add(2, y=3)
Out[9]: 5
在混合使用时,需要注意不能给同一个值赋值多次,类似于add(2, x=3)的形式会抛出异常。
3. 带默认参数的函数
我们可以给函数参数设定默认值,默认参数需要在定义设定。
例如,定义一个一元二次函数,并让参数a、b、c带有默认值:
定义时,所有带默认值的参数必须放在不带默认值的参数后面。
只传入x,其他的都用默认参数:
In [11]: quad(2.0)
Out[11]: 4.0
传入x和b:
In [12]: quad(2.0, b=3)
Out[12]: 10.0
传入x、a和c,参数b用默认:
In [13]: quad(2.0, 2, c=4)
Out[13]: 12.0
4. 接受不定数目参数的函数
有些函数可以接受不定数目的参数,如max()和min()等。不定数目参数的功能,可以在定义函数时使用星号“*”来实现。
例如,我们修改add()函数,使其能实现两个或多个值的相加:
参数中的*ys是一个可变数目的参数,我们可以把它看成一个元组。
调用add(1, 2, 3, 4)时,第一个参数1传给了x,剩下的参数组成一个元组传给了ys,因此,ys的值为(2, 3, 4)。
我们还可以使用任意键值作为参数,这种功能可以在函数定义时使用两个星号“**”实现:
**ys表示这是一个不定名字的参数,它本质上是一个字典。
调用add(1, y=2, z=3, w=4)时,ys为字典{'y': 2, 'z': 3, 'w': 4}。
这两种模式可以混用,不过要按顺序传入参数,先传入位置参数,后传入关键字参数:
反过来,我们可以在元组或者字典前加星号,将其作为参数传递给函数,不过这样的用法不是特别常见:
5. 返回多个值的函数
函数可以返回多个值。例如,下面的函数返回一个序列的最大值和最小值:
事实上,Python返回的是一个元组,只不过元组的括号被省略了。对于返回的元组,我们使用了Python的多变量赋值机制将它赋给了两个值:
In [30]: min_max(t)
Out[30]: (1, 9)
2.4.2 模块
1. 模块简介
退出IPython解释器重新启动时,之前定义的函数和变量都会丢失。如果要写一个稍微长一点的程序,使用解释器就不是那么方便,这就需要使用脚本模式进行处理。
在使用脚本模式时,随着代码量的增多,我们可能需要将一个文件切分成多个,以便管理和维护。在多个文件中使用一些公共的函数和变量时,我们不希望在每个文件里都复制粘贴一份定义。Python提供了模块机制来完成这种功能。
使用模块机制需要将公用的函数和变量放到一个文件中,然后从别的脚本或者解释器模式中导入这个文件中的内容。这个包含Python函数和定义的文件就是一个模块(Module)。通过模块,我们可以复用现成的代码,减少工作量。
在Python中,所有以.py结尾的文件都可以被当作一个模块使用。
例如,有这样一个文件ex1.py:
在Ipython解释器中,可以使用魔术命令%%writefile来创建这个文件:
如果直接使用魔术命令%run来运行这个脚本,会输出结果:
In [2]: %run ex1.py
6 3.1416
这个脚本可以被当作一个模块,导入到解释器或者其他脚本中。
在Python中,导入模块使用关键字import:
import <module>
<module>是不带.py后缀的文件名,即:
In [3]: import ex1
6 3.1416
ex1是一个被导入的模块:
In [4]: ex1
<module 'ex1' from 'ex1.py'>
在导入时,Python会执行一遍模块中的所有内容,所以ex1.py文件中print的结果被输出了。
导入模块之后,ex1.py中的变量都被载入了当前命名环境中,不过,我们需要使用“<module>.<variable>”的形式来调用它们:
In [5]: ex1.PI, ex1.w
Out[5]: (3.1416, [0, 1, 2, 3])
变量可以被修改:
In [6]: ex1.PI = 3.14
可以用“<module>.<function>”的形式调用模块里面的函数:
In [7]: ex1.my_sum([2, 3, 4])
Out[7]: 9
为了提高效率,在同一个程序中,已经载入的模块再次载入时,Python并不会真正执行载入操作,即使模块的内容已经改变。
例如,再次导入模块时,程序ex1.py的结果不会再次输出:
In [8]: import ex1
在导入模块时,Python会对ex1.py文件进行一次编译,得到以.pyc结尾的文件ex1.pyc,这是Python生成的二进制程序表示。
2. __name__变量
我们在导入模块时,有时候不希望执行脚本中的某些语句,如ex1.py中的print语句等,这种效果可以借由__name__变量来实现。
__name__变量在Python文件作为脚本执行或者作为模块导入时的值有一定的差异。作为脚本执行时,变量__name__对应的值是字符串'__main__';而作为模块导入时,变量__name__对应的值是模块的名称。因此,可以在ex1.py中的print语句前面加上对__name__变量的判断语句,得到文件ex2.py:
作为脚本运行它时,条件满足:
In [9]: %run ex2.py
6 3.1416
作为模块导入它时,条件不满足,判断里面的内容不会被执行:
In [10]: import ex2
3. 模块的导入
除了import关键字直接导入模块的形式,模块还有一些其他的导入方式,我们可以根据实际需要选择合适的方式导入模块。
模块的导入方式如表2-11所示。
表2-11 模块导入方式
导入模块时,Python会优先在当前程序的工作目录中寻找模块;如果找不到,则会去Python系统的工作目录中寻找。路径的查找顺序可以用sys模块的变量sys.path查看。
默认情况下,模块会导入脚本中所有已定义的内容,这样做有可能导入了一些不需要的内容。为此,我们可以通过设置__all__变量来限制导入的内容。在Python中,__all__变量可以用来控制模块导入的内容。
例如,在ex1中,假设我们只需要导入PI、my_sum,不需要w,可以在ex1.py中,令:
__all__ = [PI, my_sum]
这样,导入ex1时,我们就只会导入PI和my_sum两个部分了。
4. dir()函数
我们可以用dir()函数查看一个模块中所包含的所有对象:
In [11]: dir(ex2)
Out[11]: ['__name__', 'my_sum', 'PI']
如果不给定参数,dir()函数返回当前已定义的所有变量。
5. 包
包(Package)是一个由多个模块组成的集合,用来管理多个模块。
一个Python包的基本结构如下:
foo是一个文件夹,其他的是文件。其中,__init__.py文件必不可少,它可以是一个空文件。可以通过调用import foo来导入__init__.py文件中的内容。
包内的两个子模块bar.py、baz.py可以通过这样的方式来调用:
使用from <package> import <item>的形式时,<item>既可以是一个子模块,也可以是模块中的一个函数或变量。
除了子模块,包内还可以含有子包。例如,foo是一个含有子包的Python包:
子包中的模块可以相互调用。假设我们在foo/format文件夹中的bar.py文件中,希望导入foo包中其他的子模块或子包,可以使用以下方式:
其中,“.”代表当前文件夹,“..”代表当前文件夹的父文件夹。
在后文中,为了方便叙述,我们将包和模块统称为模块。