第二十节 函数3

8.5 传递任意数量的实参

有时候,预先不知道函数需要接受多少个实参,好在Python允许函数从调用语句中收集任意数量的实参。

例如,来看一个制作比萨的函数,它需要接受很多配料,但无法预先确定顾客要多少种配料。下面的函数只有一个形参*toppings ,但不管调用语句提供了多少实参,这个形参会将它们统统收入囊中:

pizza.py

def make_pizza(*toppings):
    """打印顾客点的所有配料。"""
    print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

形参名*toppings 中的星号让Python创建一个名为toppings 的空元组,并将收到的所有值都封装到这个元组中。函数体内的函数调用print() 通过生成输出,证明Python能够处理使用一个值来调用函数的情形,也能处理使用三个值来调用函数的情形。它以类似的方式处理不同的调用。注意,Python将实参封装到一个元组中,即便函数只收到一个值:

('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')

现在,可以将函数调用print() 替换为一个循环,遍历配料列表并对顾客点的比萨进行描述:

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

不管收到一个值还是三个值,这个函数都能妥善处理:

Making a pizza with the following toppings:
- pepperoni

Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

不管函数收到的实参是多少个,这种语法都管用。

8.5.1 结合使用位置实参和任意数量实参

如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。

例如,如果前面的函数还需要一个表示比萨尺寸的形参,必须将其放在形参*toppings 的前面:

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

基于上述函数定义,Python将收到的第一个值赋给形参size ,并将其他所有值都存储在元组toppings 中。在函数调用中,首先指定表示比萨尺寸的实参,再根据需要指定任意数量的配料。

现在,每个比萨都有了尺寸和一系列配料,而且这些信息按正确的顺序打印出来了——首先是尺寸,然后是配料:

Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

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

8.5.2 使用任意数量的关键字实参

有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键值对——调用语句提供 了多少就接受多少。一个这样的示例是创建用户简介:你知道将收到有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数build_profile() 接受名和姓,还接受任意数量的关键字实参:

user_profile.py

def build_profile(first, last, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切。"""
    user_info['first_name'] = first
    user_info['last_name'] = last
    return user_info
user_profile = build_profile('albert', 'einstein',
                            location='princeton',
                            field='physics')
print(user_profile)

函数build_profile() 的定义要求提供名和姓,同时允许根据需要提供任意数量的名称值对。形参**user_info 中的两个星号让Python创建一个名为user_info 的空字典,并将收到的所有名称值对都放到这个字典中。在这个函数中,可以像访问其他字典那样访问user_info 中的名称值对。

在build_profile() 的函数体内,将名和姓加入了字典user_info 中(见❶),因为总是会从用户那里收到这两项信息,而这两项信息没有放到这个字典中。 接下来,将字典user_info 返回到函数调用行。

我们调用build_profile() ,向它传递名('albert' )、姓('einstein')和两个键值对(location='princeton' 和field='physics' ),并将返回的user_info 赋给变量user_profile ,再打印该变量:

{'location': 'princeton', 'field': 'physics', 'first_name': 'albert', 'last_name': 'einstein'}

在这里,返回的字典包含用户的名和姓,还有求学的地方和所学专业。调用这个函数时,不管额外提供多少个键值对,它都能正确地处理。

编写函数时,能以各种方式混合使用位置实参、关键字实参和任意数量的实参。知道这些实参类型大有裨益,因为阅读别人编写的代码时经常会见到它们。要正确地使用这些类型的实参并知道其使用时机,需要经过一定的练习。就目前而言,牢记使用最简单的方法来完成任务就好了。继续往下阅读,你就会知道在各种情况下哪 种方法的效率最高。

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

动手试一试

练习8-12:三明治

编写一个函数,它接受顾客要在三明治中添加的一系列食材。这个函数只有一个形参(它收集函数调用中提供的所有食材),并打印一 条消息,对顾客点的三明治进行概述。调用这个函数三次,每次都提供不同数量的实参。

def sanwiches(*toppings):
    print(f"\nThe sanwiches with {toppings}.")
sanwiches('butter')
sanwiches('ice cream','vagetables')
sanwiches('potato','tamato','sugar')

练习8-13:用户简介

复制前面的程序user_profile.py,在其中调用build_profile() 来创建有关你的简介。调用这个函数时,指定你的名和姓,以及三个描述你的键值对。

def build_profile(first, last, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切。"""
    user_info['first_name'] = first
    user_info['last_name'] = last
    return user_info
user_profile = build_profile('sun', 'yongdong',
                            location='beijing',
                            field='code')
print(user_profile)

练习8-14:汽车

编写一个函数,将一辆汽车的信息存储在字典中。这个函数总是接受制造商和型号,还接受任意数量的关键字实参。这样调用该函数:提供必不可少的信息,以及两个名称值对,如颜色和选装配件。这个函数必须能够像下面这样进行调用:

car = make_car('subaru', 'outback', color='blue', tow_package=True)

打印返回的字典,确认正确地处理了所有的信息。

def make_car(maker,type,**car_info):
    car_info["maker"] = maker
    car_info["type"] = type
    return car_info
my_car_info = make_car('changan','CS35',color = 'white')
print(my_car_info)

8.6 将函数存储在模块中

使用函数的优点之一是可将代码块与主程序分离。通过给函数指定描述性名称,可让主程序容易理解得多。你还可以更进一步,将函数存储在称为模块的独立文件中,再将模块导入 到主程序中。import 语句允许在当前运行的程序文件中使用模块中的代码。

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

导入模块的方法有多种,下面对每种进行简要的介绍。

8.6.1 导入整个模块

要让函数是可导入的,得先创建模块。模块是扩展名为.py的文件,包含要导入到程序中的代码。下面来创建一个包含函数make_pizza() 的模块。为此,将文件pizza.py中除函数make_pizza() 之外的其他代码删除:

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}")

接下来,在pizza.py所在的目录中创建一个名为making_pizzas.py的文件。这个文件导入刚创建的模块,再调用make_pizza() 两次:

making_pizzas.py

import pizza

pizza.make_pizza(16, 'pepperoni') ❶
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Python读取这个文件时,代码行import pizza让Python打开文件pizza.py,并将其中的所有函数都复制到这个程序中。你看不到复制的代码,因为在这个程序即将运行时,Python在幕后复制了这些代码。你只需知道,在making_pizzas.py中,可使用pizza.py中定义的所有函数。

要调用被导入模块中的函数,可指定被导入模块的名称pizza 和函数名make_pizza() ,并用句点分隔(见❶)。这些代码的输出与没有导入模块的原始程序相同:

Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

这就是一种导入方法:只需编写一条import 语句并在其中指定模块名,就可在程序中使用该模块中的所有函数。如果使用这种import 语句导入了名为module_name.py的整个模块,就可使用下面的语法来使用其中任何一个函数:

module_name.function_name()

8.6.2 导入特定的函数

还可以导入模块中的特定函数,这种导入方法的语法如下:

from module_name import function_name

通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

from module_name import function_0, function_1, function_2

对于前面的making_pizzas.py示例,如果只想导入要使用的函数,代码将类似于下 面这样:

from pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

使用这种语法时,调用函数时无须使用句点。由于在import 语句中显式地导入了函数make_pizza() ,调用时只需指定其名称即可。

8.6.3 使用as 给函数指定别名

如果要导入函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名:函数的另一个名称,类似于外号。要给函数取这种特殊外号,需要在导入它时指定。

下面给函数make_pizza() 指定了别名mp() 。这是在import 语句中使用make_pizza as mp 实现的,关键字as 将函数重命名为指定的别名:

from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')

上面的import 语句将函数make_pizza() 重命名为mp() 。在这个程序中,每当需要调用make_pizza() 时,都可简写成mp() 。Python将运行make_pizza() 中的代码,避免与这个程序可能包含的函数make_pizza() 混淆。

指定别名的通用语法如下:

from module_name import function_name as fn

8.6.4 使用as 给模块指定别名

还可以给模块指定别名。通过给模块指定简短的别名(如给模块pizza 指定别名p),让你能够更轻松地调用模块中的函数。相比于pizza.make_pizza() ,p.make_pizza() 更为简洁:

import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

上述import 语句给模块pizza 指定了别名p ,但该模块中所有函数的名称都没变。要调用函数make_pizza() ,可编写代码p.make_pizza() 而非pizza.make_pizza() 。这样不仅代码更简洁,还让你不用再关注模块名,只专注于描述性的函数名。这些函数名明确指出了函数的功能,对于理解代码而言,比模块名更重要。

给模块指定别名的通用语法如下:

import module_name as mn

8.6.5 导入模块中的所有函数

使用星号(* )运算符可让Python导入模块中的所有函数:

from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

import 语句中的星号让Python将模块pizza 中的每个函数都复制到这个程序文件中。由于导入了每个函数,可通过名称来调用每个函数,而无须使用句点表示法。  然而,使用并非自己编写的大型模块时,最好不要采用这种导入方法。这是因为如果模块中有函数的名称与当前项目中使用的名称相同,可能导致意想不到的结果: Python可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数。

最佳的做法是,要么只导入需要使用的函数,要么导入整个模块并使用句点表示法。这让代码更清晰,更容易阅读和理解。这里之所以介绍这种导入方法,只是想 让你在阅读别人编写的代码时,能够理解类似于下面的import 语句:

from module_name import *

8.7 函数编写指南

编写函数时,需要牢记几个细节。应给函数指定描述性名称,且只在其中使用小写 字母和下划线。描述性名称可帮助你和别人明白代码想要做什么。给模块命名时也 应遵循上述约定。

每个函数都应包含简要地阐述其功能的注释。该注释应紧跟在函数定义后面,并采 用文档字符串格式。文档良好的函数让其他程序员只需阅读文档字符串中的描述就 能够使用它。他们完全可以相信代码如描述的那样运行,并且只要知道函数的名称、需要的实参以及返回值的类型,就能在自己的程序中使用它。

给形参指定默认值时,等号两边不要有空格:

def function_name(parameter_0, parameter_1='default value')

对于函数调用中的关键字实参,也应遵循这种约定:

function_name(value_0, parameter_1='value')

PEP 8建议代码行的长度不要超过79字符,这样只要编辑器窗口适中,就能看到整行代码。如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体 区分开来。

大多数编辑器会自动对齐后续参数列表行,使其缩进程度与你给第一个参数列表行指定的缩进程度相同:

def function_name(
    parameter_0, parameter_1, parameter_2,
    parameter_3, parameter_4, parameter_5):
function body...

如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样将更容易知道前一个函数在什么地方结束,下一个函数从什么地方开始。

所有import 语句都应放在文件开头。唯一例外的情形是,在文件开头使用了注释来描述整个程序。

动手试一试

练习8-15:打印模型

将示例printing_models.py中的函数放在一个名为printing_functions.py的文件中。在printing_models.py的开头编写一条import 语句,并修改该文件以使用导入的函数。

from printing_functions import print_models,show_completed_models
unprinted_desighs = ['phone case','robot pendant','dodecahedron']
completed_models = []
print_models(unprinted_desighs[:],completed_models)
show_completed_models(completed_models)
print(unprinted_desighs)

练习8-16:导入

选择一个你编写的且只包含一个函数的程序,将该函数放在另一个文件中。在主程序文件中,使用下述各种方法导入这个函数,再调用它:

import module_name
from module_name import function_name
from module_name import function_name as fn
import module_name as mn
from module_name import *

练习8-17:函数编写指南

选择你在本章中编写的三个程序,确保它们遵循了本节介绍的函数编写指南。

8.8 小结

在本章中,你学习了:

如何编写函数,以及如何传递实参,让函数能够访问完成其工作所需的信息;

如何使用位置实参和关键字实参,以及如何接受任意数量的实参;

显示输出的函数和返回值的函数;

如何将函数同列表、字典、if 语句和while 循环结合起来使用;

如何将函数存储在称为模块 的独立文件中,让程序文件更简单、更易于理解。

最后,你学习了函数编写指南,遵循这些指南可让程序始终结构良好,并对你和其他人来说易于阅读。

程序员的目标之一是,编写简单的代码来完成任务,而函数有助于你实现这样的目标。它们让你编写好代码块并确定其能够正确运行后,就可置之不理。确定函数能够正确地完成其工作后,你就可以接着投身于下一个编码任务。

函数让你编写代码一次后,想重用它们多少次就重用多少次。需要运行函数中的代码时,只需编写一行函数调用代码,就可让函数完成其工作。需要修改函数的行为时,只需修改一个代码块,而所做的修改将影响调用这个函数的每个地方。

使用函数让程序更容易阅读,而良好的函数名概述了程序各个部分的作用。相对于 阅读一系列的代码块,阅读一系列函数调用让你能够更快地明白程序的作用。

函数还让代码更容易测试和调试。如果程序使用一系列的函数来完成其任务,而其中的每个函数都完成一项具体的工作,测试和维护起来将容易得多:可编写分别调用每个函数的程序,并测试每个函数是否在它可能遇到的各种情形下都能正确地运行。经过这样的测试后你就能充满信心,深信每次调用这些函数时,它们都将正确地运行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值