3.3 Python函数、模块
定义函数时,我们需要声明函数名和参数。定义函数的好处是,对函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无须了解。
3.3.1 创建函数
先来看一个函数,该函数的函数名为hello,有一个参数name,该函数能够实现输出“Hello+name”的效果。示例代码:test3_8.py。
def hello(name): """Print Hello + name""" print("Hello {}".format(name)) if __name__ == '__main__': hello('Storm')
执行结果如下。
Hello Storm
对代码的分析如下。
●关键字(在计算机语言中又称关键词)def用来定义一个函数,后面必须跟函数名称。
●函数名称后面是括号,括号中为参数列表(可以是0个参数或多个参数),括号后面需要加上英文冒号。
●函数体语句从下一行开始,并且必须缩进。
●函数体的第一行语句是该函数的文档信息(前后用3个英文双引号引起来),为可选项。
●后面是该函数的主体功能。
3.3.2 函数参数
Python的函数定义非常简单,灵活度非常大。除了正常定义的必选参数外,函数还可以使用默认参数、可变参数和关键字参数。如果能灵活运用,定义出来的函数不但能处理复杂的操作,还可以简化调用者的代码。
(1)位置参数
我们在Python命令行写一个计算x2的函数。
>>> def power(x): ... return x*x
对于power函数,参数x就是一个位置参数。当我们调用power函数时,必须传入且仅能传入一个参数x。
>>> power(3) 9 >>> power(6) 36
现在,如果我们要计算x3该怎么办?当然,你可以再定义一个power3函数,但是如果要计算x4,x5,…怎么办?我们不可能定义无限多个函数。你也许想到了,可以把power(x)修改为power(x, n),用来计算xn。示例代码:test3_9.py。
def power(x, n): '''定义一个函数,计算x的n次方''' s = 1 while n > 0: n = n - 1 s = s * x return s if __name__ == '__main__': print(power(3,2)) print(power(2,3))
修改后的power函数可以计算某个数值的任意n次方。
修改后的power函数有两个参数:x和n。这两个参数都是位置参数,调用函数时,传入的两个值需要按照顺序依次赋给参数x和n。
(2)默认参数
新的power函数定义没有问题,但是旧的调用代码失败了。原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用。
power(3)
执行结果如下。
Traceback (most recent call last): File "D:/Love/Chapter_3/test3_2.py", line 11, in <module> power(3) TypeError: power() missing 1 required positional argument: 'n'
执行结果的错误信息很明确:调用函数power时缺少了一个位置参数n。假如我们希望参数未传递的时候,默认计算平方(在日常生活中计算平方的场景更多些),该如何做呢?这时候,默认参数就派上用场了。由于我们经常计算x2,因此我们可以把第二个参数n的默认值设定为2。示例代码:test3_10.py。
def power(x, n=2): '''定义一个函数,计算x的n次方,当n未传递时,n=2''' s = 1 while n > 0: n = n - 1 s = s * x return s if __name__ == '__main__': # print(power(3,2)) # print(power(2,3)) print(power(3)) print(power(3,2))
这样,当我们调用函数,只传递一个参数时,n就取默认值。例如,power(3)相当于power(3, 2),如图3-1所示。
图3-1 power(3)和power(3,2)
而对于n≠2的其他情况,就必须明确地传入n,如power(3, 3)。
从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有以下几点要注意。
●必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面)。
●当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面,变化小的参数就可以作为默认参数。
使用默认参数有什么好处?默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
(3)可变参数
在Python中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个或任意多个,还可以是0个。
我们以数学题为例子,给定一组数字a,b,c,…,请计算a2+b2+c2+…。
要定义一个函数,必须确定输入的参数。但这里的参数的个数不确定,我们首先想到可以把a,b,c,…作为一个列表(list)或元组(tuple)传进来,这样,函数可以定义如下。
"""这里是交互式命令行代码""" >>> def calc(numbers): ... sum = 0 ... for n in numbers: ... sum = sum + n * n ... return sum
调用的时候,需要先组装出一个列表或元组。
>>> calc([1, 2, 3]) 14 >>> calc((1, 3, 5, 7)) 84
如果使用可变参数,调用函数的方式可以简化如下。
>>> calc(1, 2, 3) 14 >>> calc(1, 3, 5, 7) 84
所以,我们把函数的参数改为可变参数。
"""这里是交互式命令行代码""" >>> def calc(*numbers): ... sum = 0 ... for n in numbers: ... sum = sum + n * n ... return sum
定义可变参数和定义一个列表或元组参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个元组,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。
>>> calc(1, 2) 5 >>> calc() 0
如果已经有一个列表或者元组,要调用一个可变参数怎么办?解决方法如下。
>>> nums = [1, 2, 3] >>> calc(nums[0], nums[1], nums[2]) 14
这种写法当然是可行的,不过太烦琐了。Python允许在列表或元组前面加一个*号,把列表或元组中的元素变成可变参数传进去。
>>> nums = [1, 2, 3] >>> calc(*nums) 14
*nums表示把nums这个列表中的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
(4)关键字参数
可变参数允许传入0个或任意多个参数,这些可变参数在函数调用时会自动组装为一个元组。而关键字参数允许传入0个或任意多个含参数名的参数,这些关键字参数在函数内部会自动组装为一个字典(dictionary)。请看以下示例。
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
函数person除了接收必选参数name和age外,还接收关键字参数kw。在调用该函数时,可以只传入必选参数。
>>> person("Storm", 30) name: Storm age: 30 other: {}
当然,我们也可以传入任意个关键字参数。
"""这里是交互式命令行代码""" >>> person('SK', 35, city='Beijing') name: SK age: 35 other: {'city': 'Beijing'} >>> person('UG', 45, gender='M', job='Engineer') name: UG age: 45 other: {'gender': 'M', 'job': 'Engineer'}
关键字参数有什么用呢?它可以扩展函数的功能。例如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,程序也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,用户也可以先组装出一个字典,然后把该字典转换为关键字参数传进去。
"""这里是交互式命令行代码""" >>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, city=extra['city'], job=extra['job']) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
当然,上面的复杂调用可以用简化的写法。
"""这里是交互式命令行代码""" >>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, **extra) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra表示把extra这个字典中的所有键-值对用关键字参数传入函数的**kw参数,kw将获得一个字典。注意,kw获得的字典是extra的一份复制结果,对kw的改动不会影响到函数外的extra。
(5)命名关键字参数
函数的调用者可以传入任意不受限制的关键字参数,至于到底传入了哪些,就需要在函数内部通过kw检查。
仍以person函数为例,我们希望检查是否有city和job参数。
def person(name, age, **kw): if 'city' in kw: # 有city参数 pass if 'job' in kw: # 有job参数 pass print('name:', name, 'age:', age, 'other:', kw)
但是调用者仍可以传入任意不受限制的关键字参数。
"""这里是交互式命令行代码""" >>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数。例如,只接收city和job作为关键字参数,函数定义如下。
def person(name, age, *, city, job): print(name, age, city, job)
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数将被视为命名关键字参数。调用方式如下。
"""这里是交互式命令行代码""" >>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
def person(name, age, *args, city, job): print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。
"""这里是交互式命令行代码""" >>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person函数仅接收两个位置参数,所以报错。
命名关键字参数可以有默认值,从而简化调用。
def person(name, age, *, city='Beijing', job): print(name, age, city, job)
由于命名关键字参数city具有默认值,因此在调用时可不传入city参数。
"""这里是交互式命令行代码""" >>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer
使用命名关键字参数时要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法区分位置参数和命名关键字参数。
def person(name, age, city, job): '''缺少*,city和job被视为位置参数''' pass
(6)参数组合
在Python中定义函数时,可以使用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数可以组合使用。但是请注意,参数定义的顺序必须是必选参数、默认参数、可变参数、命名关键字参数、关键字参数。
例如定义一些函数,包含上述若干种参数,代码如下。
"""这里是交互式命令行代码""" def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函数调用的时候,Python解释器会自动按照参数位置和参数名把对应的参数传进去。
"""这里是交互式命令行代码""" >>> f1(1, 2) a = 1 b = 2 c = 0 args = () kw = {} >>> f1(1, 2, c=3) a = 1 b = 2 c = 3 args = () kw = {} >>> f1(1, 2, 3, 'a', 'b') a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} >>> f1(1, 2, 3, 'a', 'b', x=99) a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} >>> f2(1, 2, d=99, ext=None) a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
比较灵活的用法是,可以通过元组或字典来调用上述函数。
"""这里是交互式命令行代码""" >>> args = (1, 2, 3, 4) >>> kw = {'d': 99, 'x': '#'} >>> f1(*args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} >>> args = (1, 2, 3) >>> kw = {'d': 88, 'x': '#'} >>> f2(*args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
注意▶ 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
本节小结如下。
●Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
●默认参数一定要用不可变对象,如果是可变对象,程序运行时会出现逻辑错误。
●要注意定义可变参数和关键字参数的语法:*args是可变参数,args接收的是一个元组;**kw是关键字参数,kw接收的是一个字典。
●注意调用函数时传入可变参数和关键字参数的语法:可变参数既可以直接传入[如func(1, 2, 3)],也可以先组装列表或元组,再通过*args传入[如func(*(1, 2, 3))];关键字参数既可以直接传入[如func(a=1, b=2)],也可以先组装字典,再通过**kw传入[如func(**{'a': 1, 'b': 2})]。
●*args和**kw是Python的惯用写法,当然也可以用其他参数名,但最好使用该惯用写法。
●命名关键字参数是为了限制调用者可以传入的参数名,同时还可以提供默认值。
●定义命名关键字参数时在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
3.3.3 Python模块
Python模块(module)是指以“.py”结尾的Python文件。模块用来定义函数、类和变量。把相关的代码放到一个模块中,能让代码变得更有逻辑性。文件test3_11.py包含以下内容,你可以将它看作一个模块。
def say_hello(name): print('Hello: {}'.format(name))
模块定义好后,我们可以使用import语句来导入。
●方法一:导入整个模块,导入格式为“模块名。方法名”。
import module1, module2,…
例如我们要导入math模块(test3_12.py)。
# 导入模块 import math # 然后就可以调用math模块提供的sqrt方法 print(math.sqrt(9))
●方法二:导入模块的某个方法,调用的时候直接使用方法名(test3_13.py)。
# 从math模块导入sqrt方法 from math import sqrt # 直接使用sqrt方法 print(sqrt(9))
●方法三:使用通配符*一次导入模块的所有方法(test3_14.py)。
# 从math模块导入所有方法 from math import * # 直接使用sqrt、sin方法 print(sqrt(9)) print(sin(180))