Python 函数(传递实参)
在本章中,你将学习编写函数 。函数是带名字的代码块,用于完成具体的工作。
要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,你无需反复编写完成该任务的代码,而只需调用执行该任务的函数,让Python运行
其中的代码。你将发现,通过使用函数,程序的编写、阅读、测试和修复都将更容易。
在本章中,你还会学习向函数传递信息的方式。你将学习如何编写主要任务是显示信息的函数,还有用于处理数据并返回一个或一组值的函数。最后,你将学习如何
将函数存储在被称为模块 的独立文件中,让主程序文件的组织更为有序。
传递实参
鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参 ,这要求实参的顺序与形参的顺序相同;也可使用关键
字实参 ,其中每个实参都由变量名和值组成;还可使用列表和字典。下面来依次介绍这些方式。
位置实参
你调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式被称为位置实参 。
为明白其中的工作原理,来看一个显示宠物信息的函数。这个函数指出一个宠物属于哪种动物以及它叫什么名字,如下所示:
❶ def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
❷ describe_pet('hamster', 'harry')
这个函数的定义表明,它需要一种动物类型和一个名字(见❶)。调用describe_pet() 时,需要按顺序提供一种动物类型和一个名字。例如,在前面的函数调用中,实
参’hamster’ 存储在形参animal_type 中,而实参’harry’ 存储在形参pet_name 中(见❷)。在函数体内,使用了这两个形参来显示宠物的信息。
输出描述了一只名为Harry的仓鼠:
I have a hamster.
My hamster's name is Harry.
调用函数多次
你可以根据需要调用函数任意次。要再描述一个宠物,只需再次调用describe_pet() 即可:
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')
第二次调用describe_pet() 函数时,我们向它传递了实参’dog’ 和’willie’ 。与第一次调用时一样,Python将实参’dog’ 关联到形参animal_type ,并将实参’willie’ 关联到形参pet_name 。与前面一样,这个函数完成其任务,但打印的是一条名为Willie的小狗的信息。至此,我们有一只名为Harry的仓鼠,还有一条名为Willie的
小狗:
I have a hamster.
My hamster's name is Harry.
I have a dog.
My dog's name is Willie.
调用函数多次是一种效率极高的工作方式。我们只需在函数中编写描述宠物的代码一次,然后每当需要描述新宠物时,都可调用这个函数,并向它提供新宠物的信息。即便描述
宠物的代码增加到了10行,你依然只需使用一行调用函数的代码,就可描述一个新宠物。
在函数中,可根据需要使用任意数量的位置实参,Python将按顺序将函数调用中的实参关联到函数定义中相应的形参。
位置实参的顺序很重要
使用位置实参来调用函数时,如果实参的顺序不正确,结果可能出乎意料:
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet('harry', 'hamster')
在这个函数调用中,我们先指定名字,再指定动物类型。由于实参’harry’ 在前,这个值将存储到形参animal_type 中;同理,‘hamster’ 将存储到形参pet_name 中。
结果是我们得到了一个名为Hamster 的harry :
I have a harry.
My harry's name is Hamster.
如果结果像上面一样搞笑,请确认函数调用中实参的顺序与函数定义中形参的顺序一致。
关键字实参
关键字实参 是传递给函数的名称—值对。你直接在实参中将名称和值关联起来了,因此向函数传递实参时不会混淆(不会得到名为Hamster的harry这样的结果)。关键字实参让
你无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
下面来重新编写pets.py,在其中使用关键字实参来调用describe_pet() :
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet(animal_type='hamster', pet_name='harry')
函数describe_pet() 还是原来那样,但调用这个函数时,我们向Python明确地指出了各个实参对应的形参。看到这个函数调用时,Python知道应该将实参’hamster’
和’harry’ 分别存储在形参animal_type 和pet_name 中。输出正确无误,它指出我们有一只名为Harry的仓鼠。
关键字实参的顺序无关紧要,因为Python知道各个值该存储到哪个形参中。下面两个函数调用是等效的:
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')
注意 使用关键字实参时,务必准确地指定函数定义中的形参名。
默认值
编写函数时,可给每个形参指定默认值 。在调用函数中给形参提供了实参时,Python将使用指定的实参值;否则,将使用形参的默认值。因此,给形参指定默认值后,可在函数
调用中省略相应的实参。使用默认值可简化函数调用,还可清楚地指出函数的典型用法。
例如,如果你发现调用describe_pet() 时,描述的大都是小狗,就可将形参animal_type 的默认值设置为’dog’ 。这样,调用describe_pet() 来描述小狗时,就可不
提供这种信息:
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet(pet_name='willie')
这里修改了函数describe_pet() 的定义,在其中给形参animal_type 指定了默认值’dog’ 。这样,调用这个函数时,如果没有给animal_type 指定值,Python将把这个
形参设置为’dog’ :
I have a dog.
My dog's name is Willie.
请注意,在这个函数的定义中,修改了形参的排列顺序。由于给animal_type 指定了默认值,无需通过实参来指定动物类型,因此在函数调用中只包含一个实参——宠物的名字。然而,Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参。这就是需要将pet_name 放在形参列表
开头的原因所在。
现在,使用这个函数的最简单的方式是,在函数调用中只提供小狗的名字:
describe_pet('willie')
这个函数调用的输出与前一个示例相同。只提供了一个实参——‘willie’ ,这个实参将关联到函数定义中的第一个形参——pet_name 。由于没有给animal_type 提供实
参,因此Python使用其默认值’dog’ 。
如果要描述的动物不是小狗,可使用类似于下面的函数调用:
describe_pet(pet_name='harry', animal_type='hamster')
由于显式地给animal_type 提供了实参,因此Python将忽略这个形参的默认值。
注意 使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的实参。这让Python依然能够正确地解读位置实参。
等效的函数调用
鉴于可混合使用位置实参、关键字实参和默认值,通常有多种等效的函数调用方式。请看下面的函数describe_pets() 的定义,其中给一个形参提供了默认值:
def describe_pet(pet_name, animal_type='dog'):
基于这种定义,在任何情况下都必须给pet_name 提供实参;指定该实参时可以使用位置方式,也可以使用关键字方式。如果要描述的动物不是小狗,还必须在函数调用中
给animal_type 提供实参;同样,指定该实参时可以使用位置方式,也可以使用关键字方式。
下面对这个函数的所有调用都可行:
# 一条名为Willie的小狗
describe_pet('willie')
describe_pet(pet_name='willie')
# 一只名为Harry的仓鼠
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
这些函数调用的输出与前面的示例相同。
注意 使用哪种调用方式无关紧要,只要函数调用能生成你希望的输出就行。使用对你来说最容易理解的调用方式即可。
避免实参错误
等你开始使用函数后,如果遇到实参不匹配错误,不要大惊小怪。你提供的实参多于或少于函数完成其工作所需的信息时,将出现实参不匹配错误。例如,如果调用函
数describe_pet() 时没有指定任何实参,结果将如何呢?
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet()
Python发现该函数调用缺少必要的信息,而traceback指出了这一点:
Traceback (most recent call last):
❶ File "pets.py", line 6, in <module>
❷ describe_pet()
❸ TypeError: describe_pet() missing 2 required positional arguments: 'animal_
type' and 'pet_name'
在❶处,traceback指出了问题出在什么地方,让我们能够回过头去找出函数调用中的错误。在❷处,指出了导致问题的函数调用。在❸处,traceback指出该函数调用少两个实参,
并指出了相应形参的名称。如果这个函数存储在一个独立的文件中,我们也许无需打开这个文件并查看函数的代码,就能重新正确地编写函数调用。
Python读取函数的代码,并指出我们需要为哪些形参提供实参,这提供了极大的帮助。这也是应该给变量和函数指定描述性名称的另一个原因;如果你这样做了,那么无论对于
你,还是可能使用你编写的代码的其他任何人来说,Python提供的错误消息都将更有帮助。
如果提供的实参太多,将出现类似的traceback,帮助你确保函数调用和函数定义匹配。