《python编程:入门到实践》第八章 函数

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

1.1 定义函数

使用关键字 def 来告诉 Python 你要定义一个函数。这是函数定义 ,向Python 指出了函数名,还可能在括号内指出函数为完成其 任务需要什么样的信息。
def greet_user():
  print("Hello!")

greet_user()
在这里,函数名为 greet_user() ,它不需要任何信息就能完成其工作,因此括号是空的(即便如此,括号也必不可少)。
最后,定义以冒号结尾
紧跟在 def greet_user(): 后面的所有缩进行构成了函数体。
代码行 print("Hello!") 是该函数体内的唯一一行代码。
greet_user() 只做一项工作:调用函数greet_user() ,因此将打印 Hello!

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

约定。
1.1.1 向函数传递信息

引入两个概念:形参和实参。

实参是在函数调用中传递给函数的具体数值或对象,而形参是在函数定义时声明的用于接收实参的变量或参数。简单来说,实参是传递给函数的值,而形参是函数中用来接收这些值的变量。

例如:

def greet_user(username):
 print("Hello, " + username.title() + "!")
 
greet_user('jesse')

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

1.2 传递实参

1.2.1 位置实参
调用函数时, 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')
这个函数的定义表明,它需要一个type和一个name 。因此,调用 describe_pet() 时,需要按顺序提供一个type和一个name。例子中实参'hamster' 存储在形参 animal_type 中,而实参 'harry' 存储在形参 pet_name 。在函数体内,使用了这两个形参来显示宠物的信息。故输出结果如下:

 注意:实参顺序与形参顺序必须保持一致,不然可能会闹笑话咯~

1.2.2 关键字实参
关键字实参 是传递给函数的 名称— 值 对。你直接在实参中将名称和值关联起来了,因此向函数传递实参时不会混淆(不会得到名为 Hamster harry 这样的结果)。
关键字实参让你无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
现在我们修改刚才的代码,如下所示:
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')

animal_type='hamster', pet_name='harry' 明确地指出了各个实参对应的形参。

关键字实参的顺序无关紧要,因为Python知道各个值该存储到哪个形参中。

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

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

注意:Python依然将这个实参视为位置实参。使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的实参。这让Python依然能够正确地解读位置实参。

# 一条名为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')
基于这种定义,在任何情况下都必须给 pet_name 提供实参;指定该实参时可以使用位置方式,也可以使用关键字方式。
如果要描述的动物不是小狗,还必须在函数调用中 给animal_type 提供实参;同样,指定该实参时可以使用位置方式,也可以使用关键字方式。

1.3 返回值

在函数中,可使用 return 语句将值返回到调用函数的代码行。
返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序。
1.3.1 返回简单值
def get_formatted_name(first_name, last_name):
 full_name = first_name + ' ' + last_name
 return full_name.title()

musician = get_formatted_name('jimi', 'hendrix')
print(musician)
函数 get_formatted_name() 的定义通过形参接受名和姓 。它将姓和名合而为一,在它们之间加上一个空格,并将结果存储在变量 full_name 。然后title()将full_name 的值转换为首字母大写格式,并将结果返回到函数调用行
1.3.2 让实参变成可选

还是上面的例子,但假设我们要扩展函数get_formatted_name() ,使其还需要处理中间名。

def get_formatted_name(first_name, middle_name, last_name): 
 full_name = first_name + ' ' + middle_name + ' ' + last_name
 return full_name.title()
只要同时提供名、中间名和姓,这个函数就能正确地运行。然而,并非所有的人都有中间名,但如果你调用这个函数时只提供了名和姓,它将不能正确地运行。为让中间名变成可选的,可给实参middle_name 指定一个默认值——空字符串,并在用户没有提供中间名时不使用这个实参。为让get_formatted_name() 在没有提供中间名时依然可行,可给实参middle_name 指定一个默认值——空字符串,并将其移到形参列表的末尾:
def get_formatted_name(first_name, last_name, middle_name=''):
  if middle_name:
   full_name = first_name + ' ' + middle_name + ' ' + last_name
  else:
   full_name = 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)
1.3.3 返回字典

函数可返回任何类型的值,包括列表和字典等较复杂的数据结构。

同样还是用姓名的例子简单版本,我们增加一个年龄信息,但使用字典。

def build_person(first_name, last_name, age=''):
 person = {'first': first_name, 'last': last_name}
 if age:
  person['age'] = age
 return person
musician = build_person('jimi', 'hendrix', age=27)
print(musician)

 1.4 传递列表

向函数传递列表很有用,这种列表包含的可能是名字、数字或更复杂的对象(如字典)。将列表传递给函数后,函数就能直接访问其内容。
例如:
def greet_users(names):
 for name in names:
  msg = "Hello, " + name.title() + "!"
  print(msg)
usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)
我们将 greet_users() 定义成接受一个名字列表,并将其存储在形参 names 中。这个函数遍历收到的列表,并对其中的每位用户都打印一条问候语。 我们定义了一个用户列表—— usernames ,然后调用 greet_users() ,并将这个列表传递给它。
1.4.1 在函数中修改列表
将列表传递给函数后,函数就可对其进行修改。在函数中对这个列表所做的任何修改都是永久性的,这让你能够高效地处理大量的数据。
例如:
一家为用户提交的设计制作 3D 打印模型的公司。需要打印的设计存储在一个列表中,打印后移到另一个列表中。下面是在不使用函数的情况下模拟这个过程的代码:
def print_models(unprinted_designs, completed_models):
 """
 模拟打印每个设计,直到没有未打印的设计为止
 打印每个设计后,都将其移到列表completed_models中
 """
 while unprinted_designs:
  current_design = unprinted_designs.pop()
 # 模拟根据设计制作3D打印模型的过程
  print("Printing model: " + current_design)
  completed_models.append(current_design)
def show_completed_models(completed_models):
 """显示打印好的所有模型"""
 print("\nThe following models have been printed:")
 for completed_model in completed_models:
  print(completed_model)

unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron']
completed_models = []

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
我们首先定义了函数 print_models() ,它包含两个形参:一个需要打印的设计列表和一个打印好的模型列表。给定这两个列表,这个函数模拟打印每个设计的过程:将设计逐个地从未打印的设计列表中取出,并加入到打印好的模型列表中。
其次, 我们再定义了函数 show_completed_models() ,它包含一个形参:打印好的模型列表。给定这个列表,函数show_completed_models() 显示打印出来的每个模型的名称。
这个程序的输出组织更为有序。完成大部分工作的代码都移到了两个函数中,让主程序更容易理解。每个函数都应只负责一项具体的工作。第一个函数打印每个设计,而第二个显示打印好的模型;这优于使用一个函数来完成两项工作。
编写函数时,如果你发现它执行的任务太多,请尝试将这些代码划分到两个函数中。别忘了,总是可以在一个函数中调用另一个函数,这有助于将复杂的任务划分成一系列的步骤。
1.4.2  禁止函数中修改列表
有时候,需要禁止函数修改列表。例如,假设像前一个示例那样,你有一个未打印的设计列表,并编写了一个将这些设计移到打印好的模型列表中的函数。但可能即便打印所有设计后,也要保留原来的未打印的设计列表,以供备案。但由于你将所有的设计都移出了unprinted_designs ,这个列表变成了空的,原来的列表没有了。为解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件。

因此,我们可以使用 切片表示法[:] 创建列表的副本。要将列表的副本传递给函数,可以像下面这样做:

function_name(list_name[:])

在上一示例中,如果不想清空未打印的设计列表,可像下面这样调用print_models()

print_models(unprinted_designs[:], completed_models)
这样函数 print_models() 依然能够完成其工作,因为它获得了所有未打印的设计的名称,但它使用的是列表 unprinted_designs 的副本,而不是列 表unprinted_designs 本身。像以前一样,列表 completed_models 也将包含打印好的模型的名称,但函数所做的修改不会影响到列表 unprinted_designs
虽然向函数传递列表的副本可保留原始列表的内容,但除非有充分的理由需要传递副本,否则还是应该将原始列表传递给函数,因为让函数使用现成列表可避免花时间和内存创
建副本,从而提高效率,在处理大型列表时尤其如此。

1.5 传递任意数量的实参

如果预先不知道函数需要接受多少个实参,那么需要 Python 允许函数从调用语句中收集任意数量的实参。

解决方式也很简单,举例如下:

一个制作比萨的函数,它需要接受很多配料,但你无法预先确定顾客要多少种配料。

def make_pizza(*toppings):
 """概述要制作的比萨"""
 print("\nMaking a pizza with the following toppings:")
 for topping in toppings:
  print("- " + topping)
     
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

 

形参名 *toppings 中的星号让 Python 创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中。函数体内的 print 语句通过生成输出来证明 Python 能够处理 使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形。
1.5.1 结合 位置形参 和 任意数量形参
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后 Python 先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
例如:
如果上一示例的比萨还需要一个表示比萨尺寸的实参,则必须将该形参放在形参 *toppings 的前面:
def make_pizza(size, *toppings):
 """概述要制作的比萨"""
 print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")
 for topping in toppings:
  print("- " + topping)
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

 1.5.2 使用任意数量的关键字实参*
有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键 值对 —— 调用语句提供了多少就接
受多少。
例如:
创建用户简介:你知道你将收到有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数 build_profile() 接受名和姓,同时还接受 任意数量的关键字实参:
def build_profile(first, last, **user_info):
 """创建一个字典,其中包含我们知道的有关用户的一切"""
 profile = {}
 profile['first_name'] = first
 profile['last_name'] = last
 for key, value in user_info.items():
  profile[key] = value
 return profile
user_profile = build_profile('albert', 'einstein',
                             location='princeton', field='physics')
print(user_profile)

结果为:

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

build_profile() 的函数体内,我们创建了一个名为 profile 的空字典,用于存储用户简介。for循环中, 我们遍历字典 user_info 中的键 值对,并将每个键 值对都加入到字典 profile 中。最后,我们将字典 profile 返回给函数调用行。
要正确地使用这些类型的实参并知道它们的使用时机,刚开始学习有一定困难,需要经过一定的练习。

 1.6 将函数存储在模块中

函数的优点之一是,使用它们可将代码块与主程序分离。模块的作用相当于将函数再打包于独立文件中,使用import语句再将模块导入主程序。(类似于Java中的包,可以联想Java的分层思想)
1.6.1 导入整个模块
模块是扩展名为 .py 的文件,包含要导入到程序中的代码。
下面来创建一个包含函数 make_pizza() 的模块。为此,我们将文件 pizza.py 中除函数make_pizza() 之外的其他代码都删除:
def make_pizza(size, *toppings):
 """概述要制作的比萨"""
 print("\nMaking a " + str(size) +"-inch pizza with the following toppings:")
 for topping in toppings:
  print("- " + topping)
接下来,我们在 pizza.py 所在的目录中创建另一个名为 making_pizzas.py 的文件,这个文件导入刚创建的模块,再调用 make_pizza() 两次:
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 中定义的所有函数。
如需导入所有函数可以 使用星号( * )运算符 导入模块中的所有函数:
from module_name import *

注意:由于导入了每个函数,可通过名称来调用每个函数,而无需使用句点表示法。然而,使用 并非自己编写的大型模块时,最好不要采用这种导入方法:如果模块中有函数的名称与你的项目中使用的名称相同,可能导致意想不到的结果:Python可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数。

最佳的做法是,要么只导入你需要使用的函数,要么导入整个模块并使用句点表示法。这能让代码更清晰,更容易阅读和理解。

1.6.2 导入指定函数

使用以下语法导入特定函数,用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

from module_name import function_0, function_1, function_2
1.6.3 使用as给函数指定别名
如果要导入的函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名 (可以理解为 “起外号”)。要给函数指定这种特殊外
号,需要在导入它时这样做。
通用语法如下:
from module_name import function_name as fn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙雨敲代码ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值