本页面仅用于内容预览,不包含动画交互效果
讲义加载中,请耐心等待……
Python 编程:从入门到实践Python 编程:从入门到实践(第三版)(第三版)Teacher Name / Email1
与《Python 编程:从入门到实践(第三版)》一书配套使用讲义中的文本及绘图采用署名-非商业性使用-相同方式共享协议CC BY-NC-SA 4.0进行许可引用的网络图片附有超链接,可用于访问来源讨论、意见、答疑、勘误、更新:https://github.com/scruel/pcc_3e_slides作者:@Scruel Tao关于本讲义关于本讲义2
88 函数函数8.1 定义函数8.2 传递实参8.3 返回值8.4 传递列表8.5 传递任意数量的实参8.6 将函数存储在模块中8.7 函数编写指南8.8 小结3
8.18.1 定义函数定义函数函数(function:带名字的代码块,用于完成具体的工作要执行函数定义的特定任务,可调用(call该函数本章节的概念较多,还请不要眨眼!def greet_user(): """显示简单的问候语""" print("Hello!")greet_user()4

当需要在程序中多次执行同一项任务时,无须反复编写完成该任务的代码,只需要调用执行该任务的函数,让 Python 运行其中的代码即可。你将发现,使用函数,程序编写、阅读、测试和修复起来都会更容易。

8.18.1 定义函数定义函数函数(function:带名字的代码块,用于完成具体的工作def greet_user(): """显示简单的问候语""" print("Hello!")greet_user()def 关键字:告诉 Python 你要定义一个函数,后面跟随函数定义函数定义:向 Python 指出函数名,并在紧跟着的括号中指出函数需要什么额外信息,定义以冒号 : 结尾。函数名和变量名一样,同样使用下划线(蛇形)命名法5

括号必不可少。示例的函数比较简单,不需要任何额外信息就能完成工作,因此括号内是空的。

8.18.1 定义函数定义函数函数(function:带名字的代码块,用于完成具体的工作def greet_user(): """显示简单的问候语""" print("Hello!")greet_user()函数体 :紧跟在函数定义下的所有缩进行构成的部分第二行文本是称为文档字符串(docstring)的注释,描述函数做什么第三行是示例函数的函数体中唯一一行代码,用于打印 Hello!6

Python 在为程序中的函数生成文档时,会查找紧跟在函数定义后的字符串注释。文档字符串通常前后分别用三个双引号引起,能够包含多行。

8.18.1 定义函数定义函数函数(function:带名字的代码块,用于完成具体的工作def greet_user(): """显示简单的问候语""" print("Hello!")greet_user()函数调用:让 Python 执行函数中的代码,调用函数需要依次指定函数名,以及用括号括起的必要信息示例函数的最后一行即是函数调用,它不需要额外的信息(括号需要保留)运行结果Hello!7

括号必不可少。示例的函数比较简单,不需要任何额外信息就能完成工作,因此括号内是空的。

8.18.1 定义函数定义函数函数(function:带名字的代码块,用于完成具体的工作8
8.1.28.1.2 实参和形参实参和形参只需稍作修改,就可让函数在问候用户时带上用户名:我们在函数定义的括号内添加了 username 表明函数需要的信息,并在调用函数时候,为其提供了具体的额外信息 'jesse'def greet_user(username): """显示简单的问候语""" print(f"Hello, {username.title()}!")greet_user('jesse')运行结果Hello, Jesse!9
8.1.28.1.2 实参和形参实参和形参10
8.1.28.1.2 实参和形参实参和形参只需稍作修改,就可让函数在问候用户时带上用户名:形参(parameterusername,即函数完成工作所需的信息。def greet_user(username): """显示简单的问候语""" print(f"Hello, {username.title()}!")greet_user('jesse')运行结果Hello, Jesse!11
8.1.28.1.2 实参和形参实参和形参只需稍作修改,就可让函数在问候用户时带上用户名:形参(parameterusername,即函数完成工作所需的信息。实参(argument 'jesse' ,即在调用函数时传递的信息。def greet_user(username): """显示简单的问候语""" print(f"Hello, {username.title()}!")greet_user('jesse')运行结果Hello, Jesse!12

示例中,我们将实参 'jesse' 传递给函数 greet_user(),这个值被赋给了形参 username。

大家有时候会形参、实参不分,即便你遇到了也不要大惊小怪,下面展示一种形象的记忆法。

8.1.28.1.2 实参和形参实参和形参13

记忆实参和形参的形象记忆法,简单介绍记忆

8.28.2 传递实参传递实参函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参,Python 会将每个实参关联到函数定义中的一个形参上去。传递位置实参这要求实参的顺序与形参的顺序相同;传递关键字实参其中每个实参都由变量名和值组成;14
8.2.18.2.1 位置实参位置实参位置实参:基于实参的顺序进行关联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('cat', 'harry')运行结果I have a cat.My cat's name is Harry.15

书中单词 hamster 的意思是仓鼠,这里改为了熟知的单词 cat

8.2.18.2.1 位置实参位置实参如果我们混淆了位置实参的顺序,那就可能会得到一个怪物: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', 'cat')运行结果I have a harry.My harry's name is Cat.16
8.2.18.2.1 位置实参位置实参如果我们混淆了位置实参的顺序,那就可能会得到一个怪物: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', 'cat')运行结果I have a harry.My harry's name is Cat.17

在这个函数调用中,先指定名字,再指定动物类型。由于实参 'harry' 在前,这个值将被赋给形参 animal_type,而后面的 'cat' 将被赋给形参 pet_name。结果是有一个名为 Cat 的 harry

8.2.18.2.1 位置实参位置实参我们可以根据需要调用函数任意多次: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('cat', 'harry')describe_pet('dog', 'willie')运行结果I have a cat.My cat's name is Harry.I have a dog.My dog's name is Willie.18
8.2.28.2.2 关键字实参关键字实参关键字实参:基于名称将值进行关联,函数不会混淆: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='cat', pet_name='harry')describe_pet(pet_name='harry', animal_type='cat')运行结果I have a cat.My cat's name is Harry.I have a cat.My cat's name is Harry.19
8.2.38.2.3 默认值默认值在编写函数时,可以给每个形参指定默认值当函数描述的动物大多是小狗时,我们将 animal_type 默认值设置为 'dog'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')运行结果I have a dog.My dog's name is Willie.20
8.2.38.2.3 默认值默认值我们仍可以用位置实参的方式来传参,默认值不变: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('willie')运行结果I have a dog.My dog's name is Willie.21
8.2.38.2.3 默认值默认值当在调用函数时提供了实参,Python 将忽略形参的默认值(若有)例如如果要描述的动物不是小狗,可使用类似于下面的函数调用: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='harry', animal_type='cat')运行结果I have a cat.My cat's name is Harry.22
8.2.48.2.4 等效的函数调用等效的函数调用通过混用位置实参、关键字实参和默认值,我们已经看到了一些等效的函数调用方式,对于函数定义:我们必须给 pet_name 提供实参,使用位置实参和关键字实参均可,若描述的动物不是小狗,则必须给 animal_type 提供实参下面的调用方式都是可行的,输出也与前面的示例相同:def describe_pet(pet_name, animal_type='dog'): ...describe_pet('willie')describe_pet(pet_name='willie')describe_pet('harry', 'cat')describe_pet(pet_name='harry', animal_type='cat')describe_pet(animal_type='cat', pet_name='harry')23

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

8.2.58.2.5 避免实参错误避免实参错误当你提供的实参多于或少于函数完成工作所需的实参数量时:Python 发现实参不匹配,就会给出错误并用 trackback 指出来: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()运行结果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'12324

traceback 首先指出问题出在什么地方(见 1),让我们能够回过头去找出函数调用中的错误。

然后,指出导致问题的函数调用(见 2)。

最后,traceback 指出该函数调用缺少两个实参,并指出了相应形参的名称(见 3)

如果这个函数存储在一个独立的文件中,我们也许无须打开这个文件并查看函数的代码,就能重新正确地编写函数调用。

8.2.58.2.5 避免实参错误避免实参错误另外要注意,关键词实参必须在位置实参之后(申明及调用时):Python 会报告这个语法错误: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(animal_type='cat', 'harry')运行结果Traceback (most recent call last): File "pets.py", line 6, in <module> describe_pet(animal_type='cat', 'harry’) ^SyntaxError: positional argument follows keyword argument25

(本页为增补)

8.38.3 返回值返回值返回值:函数中使用返回语句来返回的一个或一组值返回值让你能够将程序的大部分繁重工作移到函数中完成,从而简化主程序return 语句:(将值)返回到调用函数的那行代码。26
8.3.18.3.1 返回简单的值返回简单的值我们可以定义一个接受名和姓,返回标准格式姓名的函数:def get_formatted_name(first_name, last_name): """返回标准格式的姓名""" full_name = f"{first_name} {last_name}" return full_name.title()musician = get_formatted_name('jimi', 'hendrix')print(musician)运行结果Jimi Hendrix27
8.3.18.3.1 返回简单的值返回简单的值28
8.3.18.3.1 返回简单的值返回简单的值29
8.3.28.3.2 让实参变成可选的让实参变成可选的为了使得函数更加完善,我们为其增加了一个形参,表示中间名:def get_formatted_name(first_name, middle_name, last_name): """返回标准格式的姓名""" full_name = f"{first_name} {middle_name} {last_name}" return full_name.title()musician = get_formatted_name('john', 'lee', 'hooker')print(musician)运行结果John Lee Hooker30
8.3.28.3.2 让实参变成可选的让实参变成可选的考虑到大家并非都有中间名,我们需要为它指定默认值,让它变成可选的:def get_formatted_name(first_name, last_name, middle_name=''): """返回标准格式的姓名""" if middle_name: full_name = f"{first_name} {middle_name} {last_name}" else: full_name = f"{first_name} {last_name}" return full_name.title()musician = get_formatted_name('jimi', 'hendrix')print(musician)musician = get_formatted_name('john', 'hooker', 'lee')print(musician)运行结果Jimi HendrixJohn Lee Hooker12331

为了让函数在没有提供中间名时依然正确运行,可给形参 middle_name 指定默认值(空字符串),并将其移到形参列表的末尾(可选参数必须放在必选参数的后面),见 1。

在 2 处,通过 if 语句来判断是否提供了中间名,如果提供了,就将名、中间名和姓合并来生成姓名,反之则只使用名和姓来生成姓名,然后将姓名修改为首字母大写的格式,最后将结果返回函数调用行。

在 3 处,我们写了两种调用方式,第一种没有中间名,第二种则传入了中间名。

可选值在让函数能够处理各种不同情形的同时,确保函数调用尽可能简单。

(为了避免混淆,更推荐使用关键字实参的调用方式,这里限于版面未使用)

8.3.38.3.3 返回字典返回字典函数可返回任何类型的值,包括列表和字典等较为复杂的数据结构:def build_person(first_name, last_name): """返回一个字典,其中包含有关一个人的信息""" person = {'first': first_name, 'last': last_name} return personmusician = build_person('jimi', 'hendrix')print(musician)运行结果{'first': 'jimi', 'last': 'hendrix'}12332

函数接受名和姓,并将这些值放在字典中(见 1)。

在存储 first_name 的值时,使用的键为 'first',而在存储 last_name 的值时,使用的键为 'last'。

然后,返回表示人的整个字典(见 2)。在 3 处,打印这个被返回的值。

8.3.38.3.3 返回字典返回字典我们可以增加一个年龄可选值,其默认值为 None ,表示没有值或是占位值:def build_person(first_name, last_name, age=None): """返回一个字典,其中包含有关一个人的信息""" person = {'first': first_name, 'last': last_name} if age: person['age'] = age return personmusician = build_person('jimi', 'hendrix', age=27)print(musician)运行结果{'first': 'jimi', 'last': 'hendrix', 'age': 27}33

在条件测试中,None 相当于 False。

8.3.48.3.4 结合使用函数和结合使用函数和 whilewhile 循环循环我们可以结合之前的知识点,以更正规的方式来问候用户:def get_formatted_name(first_name, last_name): """返回规范格式的姓名""" full_name = f"{first_name} {last_name}" return full_name.title()while True: print("\nPlease tell me your name:") f_name = input("First name: ") l_name = input("Last name: ") formatted_name = get_formatted_name(f_name, l_name) print(f"\nHello, {formatted_name}!")小心无限循环!34

在这个示例中,使用的是get_formatted_name()的简单版本,不涉及中间名。

while 循环让用户输入姓名:提示用户依次输入名和姓,随后打印出更为正规的问候语。

8.3.48.3.4 结合使用函数和结合使用函数和 whilewhile 循环循环让我们来修正刚才的问题,为用户添加一个退出程序的途径:def get_formatted_name(first_name, last_name): ...while True: print("\nPlease tell me your name:") print("(enter 'q' at any time to quit)") f_name = input("First name: ") if f_name == 'q': break l_name = input("Last name: ") if f_name == 'q': break formatted_name = get_formatted_name(f_name, l_name) print(f"\nHello, {formatted_name}!")35

我们要让用户能够尽可能容易地退出,因此在每次提示用户输入时,都应提供退出途径。使用 break 语句可以在每次提示用户输入时退出循环

8.3.48.3.4 结合使用函数和结合使用函数和 whilewhile 循环循环让我们来修正刚才的问题,为用户添加一个退出程序的途径:运行结果Please tell me your name:(enter 'q' at any time to quit)First name:Last name:Hello, Eric Matthes!Please tell me your name:(enter 'q' at any time to quit)First name: ericmatthesq36
8.48.4 传递列表传递列表假设有一个用户列表,我们要向其中的每个用户发出问候:def greet_users(names): """向列表中的每个用户发出简单的问候""" for name in names: msg = f"Hello, {name.title()}!" print(msg)usernames = ['hannah', 'ty', 'margot']greet_users(usernames)运行结果Hello, Hannah!Hello, Ty!Hello, Margot!37
8.4.18.4.1 在函数中修改列表在函数中修改列表我们可以对传入函数的列表做出永久性修改,忆及 7.3 节中的代码:unconfirmed_users = ['alice', 'brian', 'candace']confirmed_users = []while unconfirmed_users: current_user = unconfirmed_users.pop() print(f"Verifying user: {current_user.title()}") confirmed_users.append(current_user)print("\nThe following users have been confirmed:")for confirmed_user in confirmed_users: print(confirmed_user.title())38

【Note】为了节省版面,减少冗余的介绍,这里忆及原书中与本节例子几乎完全相似的代码来做演示

8.4.18.4.1 在函数中修改列表在函数中修改列表学习函数后,我们可以重新组织代码,编写两个函数来让代码结构更加合理:def verify_users(unconfirmed_users, confirmed_users): """验证每个未验证用户,并在随后移到已验证列表中""" while unconfirmed_users: current_user = unconfirmed_users.pop() print(f"Verifying user: {current_user.title()}") confirmed_users.append(current_user)def show_verified_users(confirmed_users): """显示所有的已验证用户""" print("\nThe following users have been confirmed:") for confirmed_user in confirmed_users: print(confirmed_user.title())39

第一个是 verify_users 函数,它的定义中包含两个形参,未验证用户列表和已验证用户列表,功能即验证每个用户,并将他们从未验证用户列表移入已验证用户列表。

第二个是 show_verified_users 函数,它的定义中包含一个形参,用于打印已验证用户列表中的用户信息。

8.4.18.4.1 在函数中修改列表在函数中修改列表经过重新组织,我们将复杂的数行代码,转换为了简洁的两行函数调用:unconfirmed_users = ['alice', 'brian', 'candace']confirmed_users = []verify_users(unconfirmed_users, confirmed_users)show_verified_users(confirmed_users)40
8.4.18.4.1 在函数中修改列表在函数中修改列表经过重新组织,我们将复杂的数行代码,转换为了简洁的两行函数调用:运行的结果也仍然是一样的:运行结果Verifying user: CandaceVerifying user: BrianVerifying user: AliceThe following users have been confirmed:CandaceBrianAlice41
8.4.18.4.1 在函数中修改列表在函数中修改列表相比没有使用函数的版本,程序更容易扩展和维护不再需要修改多处代码修改函数内的代码,可以影响到所有调用该函数的地方比如我们能通过在函数中修改内容或增加一行输出等方式,统一修改输出。每个函数都应只负责一项具体工作:有助于函数间的相互调用(在一个函数中调用另一个函数)有助于将复杂的任务分解成一系列步骤42

D.R.Y: Don't repeat yourself

单一职责原则

8.4.28.4.2 禁止函数修改列表禁止函数修改列表我们可以通过切片操作将复制的列表副本传递给函数,以防止原列表被修改:verify_users(unconfirmed_users[:], confirmed_users)43
8.4.28.4.2 禁止函数修改列表禁止函数修改列表我们可以通过切片操作将复制的列表副本传递给函数,以防止原列表被修改:运行结果...['alice', 'brian', 'candace']unconfirmed_users = ['alice', 'brian', 'candace']confirmed_users = []verify_users(unconfirmed_users[:], confirmed_users)show_verified_users(confirmed_users)print(unconfirmed_users)44

除非有充分的理由,否则还是应该将原始列表传递给函数。

这是因为,让函数使用现成的列表可避免花时间和内存创建副本,从而提高效率,在处理大型列表时尤其如此。

8.58.5 传递任意数量的实参传递任意数量的实参我们有时候无法确定函数需要接受多少个实参例如披萨店顾客点选的配料种数可能是不确定的,此时可以:def make_pizza(*toppings): """打印顾客点的所有配料""" print(toppings)make_pizza('pepperoni')make_pizza('mushrooms', 'green peppers', 'extra cheese')星号 * Python 创建一个名为 toppings 的元组,其中包含函数收到的所有(余下的)位置实参。45
8.58.5 传递任意数量的实参传递任意数量的实参我们有时候无法确定函数需要接受多少个实参例如披萨店顾客点选的配料种数可能是不确定的,此时可以:def make_pizza(*toppings): """打印顾客点的所有配料""" print(toppings)make_pizza('pepperoni')make_pizza('mushrooms', 'green peppers', 'extra cheese')运行结果('pepperoni',)('mushrooms', 'green peppers', 'extra cheese')46

函数体调用函数生成的输出,证明此时 Python 能够处理使用一个值调用函数的情形,也能处理使用三个值调用函数的情形。并且能知道,print() 函数是能够处理元组的。

8.58.5 传递任意数量的实参传递任意数量的实参我们有时候无法确定函数需要接受多少个实参例如披萨店顾客点选的配料种数可能是不确定的,此时可以:def make_pizza(*toppings): """打印顾客点的所有配料""" print(toppings)make_pizza('pepperoni')make_pizza('mushrooms', 'green peppers', 'extra cheese')运行结果('pepperoni',)('mushrooms', 'green peppers', 'extra cheese')47

注意运行结果的第一行,这是 Python 中仅有一个元素的元组写法(想想为什么这么写?)

8.58.5 传递任意数量的实参传递任意数量的实参当然,我们也可以将函数体用循环完善一下:def make_pizza(*toppings): """概述要制作的披萨""" print("\nMaking a pizza with the following toppings:") for topping in toppings: print(f"- {topping}")make_pizza('pepperoni')make_pizza('mushrooms', 'green peppers', 'extra cheese')48
8.58.5 传递任意数量的实参传递任意数量的实参不管收到一个值还是三个值,这个函数都能妥善地处理:运行结果Making a pizza with the following toppings:- PepperoniMaking a pizza with the following toppings:- mushrooms- green peppers- extra cheese49
8.5.18.5.1 结合使用结合使用如果要让函数接受不同类型的实参,必须在函数定义中将接受任意数量实参的形参放在最后,例如我们可以在制作前,要求顾客提供尺寸信息:def make_pizza(size, *toppings): """概述要制作的披萨""" print(f"\nMaking a {size}-inch pizza with the following toppings:") for topping in toppings: print(f"- {topping}")make_pizza(16, 'pepperoni')make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')50

Python 先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。

8.5.18.5.1 结合使用结合使用Python 将收到的第一个值赋给形参 size,将其他所有的值都存储在 toppings元组中,从而使这些信息被按正确的顺序打印出来:运行结果Making a 16-inch pizza with the following toppings:- PepperoniMaking a 12-inch pizza with the following toppings:- mushrooms- green peppers- extra cheese51

你经常会看到通用形参名 *args,它也这样收集任意数量的位置实参。

8.5.28.5.2 使用任意数量的关键字实参使用任意数量的关键字实参我们有时候需要接受任意数量的实参,但不知道信息是什么样的,例如用户会希望使用一些自定义信息来创建账户,此时可以:def build_profile(first, last, **user_info): """创建一个字典,其中包含我们知道的有关用户的一切""" user_info['first_name'] = first user_info['last_name'] = last return user_infouser_profile = build_profile('albert', 'einstein', location='princeton', field='physics')print(user_profile)双星号 ** 让 Python 创建一个名为 user_info 的字典,其中包含函数收到的所有(余下的)关键字实参。52
8.5.28.5.2 使用任意数量的关键字实参使用任意数量的关键字实参我们有时候需要接受任意数量的实参,但不知道信息是什么样的,例如用户会希望使用一些自定义信息来创建账户,此时可以:def build_profile(first, last, **user_info): """创建一个字典,其中包含我们知道的有关用户的一切""" user_info['first_name'] = first user_info['last_name'] = last return user_infouser_profile = build_profile('albert', 'einstein', location='princeton', field='physics')print(user_profile)运行结果{'location': 'princeton', 'field': 'physics', 'first_name': 'albert', 'last_name': 'einstein'}53

在函数体内,我们将名和姓加入字典 user_info,因为总是会从用户那里收到这两项信息,而这两项信息还没被放在字典中。接下来,将字典 user_info 返回函数调用行,这个字典实际上是 Python 为我们创建的。

注意:你经常会看到形参名 **kwargs,它用于收集任意数量的关键字实参。

8.68.6 将函数存储在模块中将函数存储在模块中使用函数的优点之一是可将代码块与主程序分离,通过给函数指定描述性名称(并提供文档字符串),能让程序容易理解得多。你还可以更进一步,将函数存储在称为模块(module的独立文件中,再将模块导入import)主程序。import 语句可让你在当前运行的程序文件中使用模块中的代码。导入模块的方法有好几种,下面对每种都做简要的介绍。54

通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上。这还能让你在众多不同的程序中复用函数。将函数存储在独立文件中后,可与其他程序员共享这些文件而不是整个程序。知道如何导入函数还能让你使用其他程序员编写的函数库。

8.6.18.6.1 导入整个模块导入整个模块模块是扩展名为 .py 的文件,包含要导入程序的代码。为了学习模块相关的知识,首先我们需要做一些准备工作:新建一个文件夹(比如叫做 module-demo在文件夹中新建两个文件,文件名分别为:pizza.pymaking_pizzas.py55
8.6.18.6.1 导入整个模块导入整个模块打开 pizza.py 文件,写入简单版本的披萨制作代码:def make_pizza(size, *toppings): """概述要制作的披萨""" print(f"\nMaking a {size}-inch pizza with the following toppings:") for topping in toppings: print(f"- {topping}")56
8.6.18.6.1 导入整个模块导入整个模块接着打开 making_pizzas.py 文件,写入下面的代码:注意程序的第一行 import pizza,便是导入模块语句的写法import pizzapizza.make_pizza(16, 'pepperoni')pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')57

当 Python 读取这个文件时,代码行 import pizza 会让 Python 打开文件 pizza.py,并将其中的所有函数都复制到这个程序中。

你看不到复制代码的过程,因为 Python 会在程序即将运行时在幕后复制这些代码。你只需要知道,在 making_pizzas.py 中,可通过导入的模块,使用pizza.py 中定义的所有函数。

8.6.18.6.1 导入整个模块导入整个模块接着打开 making_pizzas.py 文件,写入下面的代码:要调用被导入模块中的函数,可指定被导入模块的名称 pizza 函数名 make_pizza(),并用点号隔开。import pizzapizza.make_pizza(16, 'pepperoni')pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')58
8.6.18.6.1 导入整个模块导入整个模块这些代码的输出与没有使用模块导入方式的原始程序相同:运行结果Making a 16-inch pizza with the following toppings:- PepperoniMaking a 12-inch pizza with the following toppings:- mushrooms- green peppers- extra cheese59
8.6.18.6.1 导入整个模块导入整个模块这便是其中一种导入方法,具体语法如下:我们使用 import 语句来导入模块,并通过模块名.函数名的语法,来使用其中的任意一个函数。import module_namemodule_name.function_name()60
8.6.28.6.2 导入特定的函数导入特定的函数第二种方式是只导入模块中的特定函数,具体语法如下:from module_name import function_name如果需要导入的特定函数不止一个,可以使用逗号分隔函数名:from module_name import function_0, function_1, function_2这种语法调用函数时无须点号,我们可以把示例改写成这样:making_pizzas.pyfrom pizza import make_pizzamake_pizza(16, 'pepperoni')make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')61

由于在 import 语句中显式地导入了 make_pizza()函数,因此在调用时只需指定其名称即可。

8.6.38.6.3 使用使用 asas 给函数指定别名给函数指定别名我们可以给导入的函数起别名,具体语法如下:from module_name import function_name as fn别名(alias函数的另一个名称,类似于外号,使用 as 关键字。当要导入的函数的名称太长或者可能与程序中既有的名称冲突时适合使用例如我们可以给函数 make_pizza() 指定一个别名 mp()making_pizzas.pyfrom pizza import make_pizza as mpmp(16, 'pepperoni')mp(12, 'mushrooms', 'green peppers', 'extra cheese')62

import 语句将函数 make_pizza() 重命名为 mp()。

在这个程序中,每当需要调用 pizza 模块的 make_pizza() 时,都需要使用其别名 mp()。

Python 将运行 pizza 模块中 make_pizza() 中的代码,同时避免与程序可能包含的 make_pizza()函数混淆。

8.6.48.6.4 使用使用 asas 给模块指定别名给模块指定别名我们还可以给导入的模块起别名,语法如下:import module_name as mn例如我们可以给模块 pizza 指定别名 p,使得函数更简洁:making_pizzas.pyimport pizza as pp.make_pizza(16, 'pepperoni')p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')63

上述import 语句给 pizza 模块指定了别名 p,但该模块中所有函数的名称都没变。

要调用 make_pizza() 函数,需要将其写为 p.make_pizza() 而不是 pizza.make_pizza()。

8.6.48.6.4 使用使用 asas 给模块指定别名给模块指定别名我们更可以让 Python 导入模块中的所有函数,语法如下:from module_name import *写法与导入特定的函数方式类似,不过不需要具体的函数名调用的方式也是类似的:making_pizzas.pyfrom pizza import *make_pizza(16, 'pepperoni')make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')64

星号实际在这里充当通配符

8.6.48.6.4 使用使用 asas 给模块指定别名给模块指定别名我们更可以让 Python 导入模块中的所有函数,语法如下:from module_name import *写法与导入特定的函数方式类似,不过我们没有写具体的函数名这种方法并不推荐使用,因为可能会导致同名函数或变量的名称覆盖这里主要目的是介绍,以便你能在看到这种用法时能知道其含义推荐的最佳做法是(能让代码更清晰,更容易阅读和理解):要么只导入一个或多个需要使用的函数要么导入整个模块并使用点号语法65
8.78.7 函数编写指南函数编写指南要给函数和模块一个描述性名称,且只使用小写字母和下划线每个函数都应包含简要阐述其功能的注释,该注释应紧跟在函数定义后面,并采用文档字符串的格式给形参指定默认值和调用函数传入实参时,等号两边不要有空格def function_name(parameter_0, parameter_1='default value') : """... """function_name(value_0, parameter_1='value')66

描述性名称可帮助你和别人明白代码想要做什么。

文档字符串帮助别人了解函数的相关信息,让别人只需阅读其中的描述就能够使用它:他们完全可以相信代码会如描述的那样运行,并且只要知道函数名、需要的实参以及返回值的类型,就能在自己的程序中使用它。

8.78.7 函数编写指南函数编写指南当代码过长时,为了能让别人在编辑器窗口易于查看整行代码:应该在函数定义中,输入左括号后按回车键,然后按两次制表符键(八个空格)如果形参或实参的代码很长,应在合适的形参及其逗号后做同样的操作大多数的编辑器会自动进行对齐,如果有则无需按两次制表符键。def function_name( parameter_0, parameter_1, parameter_2, parameter_3, parameter_4, parameter_5): ...67

PEP 8 建议代码行的长度不要超过79 个字符。

8.78.7 函数编写指南函数编写指南为了能让别人在编辑器窗口适中时看到整行代码,当代码过长时:应该在函数定义中,输入左括号后按回车键,然后按两次制表符键(八个空格)如果形参或实参的代码很长,应在合适的形参及其逗号后做同样的操作大多数的编辑器会自动进行对齐,如果有则无需按两次制表符键。应使用两个空行分开不同的函数块(书中代码限于版面限制未全做到)所有的 import 语句都应放在文件开头,唯一的例外是,你要在文件开头使用注释来描述整个程序。68
8.88.8 小结小结学习了如何编写函数,以及如何传递实参,让函数能够访问完成工作所需的信息。学习了如何使用位置实参和关键字实参,以及如何接受任意数量的实参。学习了编写能够返回值的函数,以及如何将函数存储在称为模块的独立文件中,让程序文件更简单、更易于理解。在下一章中,我们将你将学习编写类。类将函数和数据整洁地封装起来,让你能够灵活而高效地使用它们。69
课后拓展课后拓展完成书中练一练部分内容配置并使用编辑器提供的格式化功能(快捷键)可选拓展了解 Python 的打包和解包了解什么是仅限位置形参和仅限形参将嵌套 if 语句放入函数,了解卫语句写法,并用它优化代码了解 Python 的作用域及关键字 nonlocal global了解什么是 typing hints70

强调一下,如果是零基础入门,可选拓展是完全可以忽略的,要避免陷入知识的泥潭哦 。

题外话,原本打算在可选拓展中写上 “学习 Python 作用域规则(LEGB)”的,而不是“初步了解 Python 的作用域……”,但考虑到本书的入门性质,还是选择了后者的表述,因为类似的知识点即便是有经验的程序员,学习时也很容易被绕晕。

当然了,如果你想要成为 Python 专家,那么阅读进阶书籍(例如《流畅的 Python》等),就是你职业发展中必不可少的一环了。