第十八节 函数1

在本章中,你将学习编写函数 。函数是带名字的代码块,用于完成具体的工作。要执行函数定义的特定任务,可调用   该函数。需要在程序中多次执行同一项任务时,无须反复编写完成该任务的代码,只需要调用执行该  任务的函数,让Python运行其中的代码即可。你将发现,通过使用函数,程序  编写、阅读、测试和修复起来都更加容易。

你还将学习向函数传递信息的方式;学习如何编写主要任务是显示信息的函数,以及旨在处理数据并返回一个或一组值的函数;最后,学习如何将函数存 储在称为模块 的独立文件中,让主程序文件的组织更为有序。

8.1 定义函数

下面是一个打印问候语的简单函数,名为greet_user() :

greeter.py

def greet_user(): ❶
    """显示简单的问候语。""" ❷
    print("Hello!") ❸
greet_user() ❹

本例演示了最简单的函数结构。❶处的代码行使用关键字def 来告诉Python,你要定义一个函数。这是函数定义 ,向Python指出了函数名,还可能在圆括号内指出函数为完成任务需要什么样的信息。在这里,函数名为greet_user(),它不需要任何信息就能完成工作,因此括号是空的(即便如此,括号也必不可少)。最后,定义以冒号结尾。

紧跟在def greet_user(): 后面的所有缩进行构成了函数体。❷处的文本是称为文档字符串(docstring)的注释,描述了函数是做什么的。文档字符串用三引号括起,Python使用它们来生成有关程序中函数的文档。

代码行print("Hello!") (见❸)是函数体内的唯一一行代码,因此greet_user() 只做一项工作:打印Hello! 。

要使用这个函数,可调用它。函数调用 让Python执行函数的代码。要调用函数, 可依次指定函数名以及用圆括号括起的必要信息,如❹处所示。由于这个函数不需要任何信息,调用它时只需输入greet_user() 即可。和预期一样,它打印Hello!

Hello!

8.1.1 向函数传递信息

只需稍作修改,就可让函数greet_user()不仅向用户显示Hello! ,还将用户的名字作为抬头。为此,可在函数定义def greet_user()的括号内添加username 。通过在这里添加username,可让函数接受你给username指定的任何值。现在,这个函数要求你调用它时给username指定一个值。调用greet_user() 时,可将一个名字传递给它,如下所示:

def greet_user(username):
    """显示简单的问候语。"""
    print(f"Hello!{username.title()}!")
greet_user('sun')

代码greet_user(sun') 调用函数greet_user() ,并向它提供执行函数调用print() 所需的信息。这个函数接受你传递给它的名字,并向这个人发出问候:

Hello!Sun!

同样,greet_user('sarah') 调用函数greet_user() 并向它传递'sarah',从而打印Hello!Sarah! 。可根据需要调用函数greet_user() 任意次,调用时无论传入什么名字,都将生成相应的输出。

8.1.2 实参和形参

前面定义函数greet_user() 时,要求给变量username 指定一个值。调用这个函数并提供这种信息(人名)时,它将打印相应的问候语。

在函数greet_user() 的定义中,变量username 是一个形参(parameter), 即函数完成工作所需的信息。在代码greet_user('jesse') 中,值'jesse' 是一个实参(argument),即调用函数时传递给函数的信息。调用函数时,将要让函数使用的信息放在圆括号内。在greet_user('jesse') 中,将实参'jesse' 传递给了函数greet_user() ,这个值被赋给了形参username。

注意大家有时候会形参、实参不分,因此如果你看到有人将函数定义中的变量称为实参或将函数调用中的变量称为形参,不要大惊小怪。

动手试一试

练习8-1:消息

编写一个名为display_message() 的函数,它打印一个句子,指出你在本章学的是什么。调用这个函数,确认显示的消息正确无误。

def display_message():
    print("本章学习函数。")
display_message()

练习8-2:喜欢的图书

编写一个名为favorite_book() 的函数,其中包含一个名为title 的形参。这个函数打印一条消息,下面是一个例子。

One of my favorite books is Alice in Wonderland.

调用这个函数,并将一本图书的名称作为实参传递给它。

def favorite_book(title):
    print(f"One of my favorite book is {title.title()}.")
favorite_book('三国演义')

8.2 传递实参

函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多:可使用位置实参,这要求实参的顺序与形参的顺序相同;也可使用关键字实参,其中每个实参都由变量名和值组成;还可使用列表和字典。下面依次介绍这些方式。

8.2.1 位置实参

调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式称为位置实参 。

为明白其中的工作原理,来看一个显示宠物信息的函数。这个函数指出一个宠物属于哪种动物以及它叫什么名字,如下所示:

pets.py

def describe_pet(animal_type, pet_name): ❶
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"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.

a. 多次调用函数

可以根据需要调用函数任意次。要再描述一个宠物,只需再次调用describe_pet() 即可:

def describe_pet(animal_type, pet_name):
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"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将按顺序将函数调用中的实参关联到函数定义中相应的形参。 

b. 位置实参的顺序很重要

使用位置实参来调用函数时,如果实参的顺序不正确,结果可能出乎意料:

def describe_pet(animal_type, pet_name):
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"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.

如果你得到的结果像上面一样可笑,请确认函数调用中实参的顺序与函数定义中形参的顺序一致。

8.2.2 关键字实参

关键字实参是传递给函数的名称值对。因为直接在实参中将名称和值关联起来,所以向函数传递实参时不会混淆(不会得到名为Hamster的harry这样的结果)。关键字实参让你无须考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。

下面来重新编写pets.py,在其中使用关键字实参来调用describe_pet() :

def describe_pet(animal_type, pet_name):
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"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')

注意使用关键字实参时,务必准确指定函数定义中的形参名。

8.2.3 默认值

编写函数时,可给每个形参指定默认值 。在调用函数中给形参提供了实参时, Python将使用指定的实参值;否则,将使用形参的默认值。因此,给形参指定默认 值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用,还可清楚地 指出函数的典型用法。

例如,如果你发现调用describe_pet() 时,描述的大多是小狗,就可将形参animal_type 的默认值设置为'dog' 。这样,调用describe_pet() 来描述小狗时,就可不提供这种信息:

def describe_pet(pet_name, animal_type='dog'):
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"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依然能够正确地解读位置实参。

8.2.4 等效的函数调用

鉴于可混合使用位置实参、关键字实参和默认值,通常有多种等效的函数调用方式。请看下面对函数describe_pet() 的定义,其中给一个形参提供了默认值:

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')

这些函数调用的输出与前面的示例相同。

注意使用哪种调用方式无关紧要,只要函数调用能生成你期望的输出就行。使用对你来说最容易理解的调用方式即可。

8.2.5 避免实参错误

等你开始使用函数后,如果遇到实参不匹配错误,不要大惊小怪。你提供的实参多于或少于函数完成工作所需的信息时,将出现实参不匹配错误。例如,如果调用函 数describe_pet() 时没有指定任何实参,结果将如何呢?

def describe_pet(pet_name, animal_type='dog'):
    """显示宠物的信息。"""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet()

Python发现该函数调用缺少必要的信息,traceback指出了这一点:

Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\6.py", line 5, in <module> ❶
    describe_pet() ❷
TypeError: describe_pet() missing 1 required positional argument: 'pet_name' ❸

在❶处,traceback指出了问题出在什么地方,让我们能够回过头去找出函数调用中 的错误。在❷处,指出了导致问题的函数调用。在❸处,traceback指出该函数调用少了两个实参,并指出了相应形参的名称。如果这个函数存储在一个独立的文件中,我们也许无须打开这个文件并查看函数的代码,就能重新正确地编写函数调用。

Python读取函数的代码并指出需要为哪些形参提供实参,这提供了极大的帮助。这也是应该给变量和函数指定描述性名称的另一个原因:如果这样做了,那么无论对于你,还是可能使用你编写的代码的其他任何人来说,Python提供的错误消息都将更帮助。

如果提供的实参太多,将出现类似的traceback,帮助你确保函数调用和函数定义匹配。

动手试一试

练习8-3:T恤

编写一个名为make_shirt() 的函数,它接受一个尺码以及要印到T恤上的字样。这个函数应打印一个句子,概要地说明T恤的尺码和字样。

使用位置实参调用该函数来制作一件T恤,再使用关键字实参来调用这个函数。

def make_shirt(size,word):
    print(f"\nThe T-shirt is {size} with the word {word}.")
make_shirt('XL','创客营')

练习8-4:大号T恤

修改函数make_shirt() ,使其在默认情况下制作一件印有“I love Python”字样的大号T恤。调用这个函数来制作:一件印有默认字样的大号T恤,一件印有默认字样的中号T恤,以及一件印有其他字样的T恤(尺码无关紧要)。

def make_shirt(size,word = 'I love Python'):
    print(f"\nThe T-shirt is {size} with the word {word}.")
make_shirt('L')
make_shirt('M')

练习8-5:城市

编写一个名为describe_city() 的函数,它接受一座城市的名字以及该城市所属的国家。这个函数应打印一个简单的句子,下面是一个例子。

Reykjavik is in Iceland.

给用于存储国家的形参指定默认值。为三座不同的城市调用这个函数,且其中至少有一座城市不属于默认国家。

def describe_city(city,country='china'):
    print(f"{city.title()} is in {country.title()}.")
describe_city('qingdao')
describe_city('london','england')
describe_city('wuhan')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值