我心中的王者:Python-第11章 函数设计
函数设计所谓的函数(function)其实就是一系列指令语句所组成,它的目的有两个。
-
当我们在设计一个大型程序时,若是能将这个程序依功能,将其分割成较小的功能,然后依这些较小功能要求撰写函数程序,如此,不仅使程序简单化,最后程序侦错也变得容易。另外,撰写大型程序时应该是团队合作,每一个人负责一个小功能,可以缩短程序开发的时间。
-
在一个程序中,也许会发生某些指令被重复书写在许多不同的地方,若是我们能将这些重复的指令撰写成一个函数,需要用时再加以调用,如此,不仅减少编辑程序的时间,更可使程序精简、清晰、明了。
下列是调用函数的基本流程图。
当一个程序在调用函数时,Python会自动跳到被调用的函数上执行工作,执行完后,会回到原先程序执行位置,然后继续执行下一道指令。
11-1 Python函数基本观念
从前面的学习相信读者已经熟悉使用Python内置的函数了,例如,len( )、add( )、remove( )等。有了这些函数,我们可以随时调用使用,让程序设计变得很简洁,这一章主题将是如何设计这类的函数。
11-1-1 函数的定义
函数的语法格式如下:
- 函数名称 名称必须是唯一的,程序未来可以调用引用。
- 参数值 这是可有可无的,完全视函数设计需要,可以接收调用函数传来的变量,各参数值之间是用逗号“,”隔开。
- 函数批注 这是可有可无的,不过如果是参与大型程序设计计划,当负责一个小程序时,建议所设计的函数需要加上批注,除了自己需要也是方便他人阅读。主要是注明此函数的功能,由于可能有多行批注所以可以用3个双引号(或单引号)包夹。许多英文Python资料称此为docstring(document
string的缩写)。
- return [返回值1,返回值2 , … ]不论是return或接续右边的返回值皆是可有可无,如果有返回多个数据彼此需以逗号“,”隔开。
11-1-2 无参数无返回值的函数
程序实例ch11_1.py:第一次设计Python函数。
# ch11_1.py
def greeting( ):
"""我的第一个Python函数设计"""
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
# 以下的程序代码也可称主程序
greeting( )
greeting( )
greeting( )
greeting( )
greeting( )
执行结果
Python欢迎你
祝福学习顺利
谢谢
Python欢迎你
祝福学习顺利
谢谢
Python欢迎你
祝福学习顺利
谢谢
Python欢迎你
祝福学习顺利
谢谢
Python欢迎你
祝福学习顺利
谢谢
在程序设计的观念中,有时候我们也可以将第8行以后的程序代码称主程序。读者可以想想看,如果没有函数功能我们的程序设计将如下所示:
程序实例ch11_2.py:重新设计ch11_1.py,但是不使用函数设计。
# ch11_2.py
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
print("Python欢迎你")
print("祝福学习顺利")
print("谢谢")
执行结果 与ch11_1.py相同。
上述程序虽然也可以完成工作,但是可以发现重复的语句太多了,这不是一个好的设计。同时如果要将“Python欢迎你”改成“Python欢迎你们”,程序必须修改5次相同的语句。经以上讲解读者应可以了解函数对程序设计的好处了吧!
11-1-3 在Python Shell执行函数
当程序执行完ch11_1.py时,在Python Shell窗口可以看到执行结果,此时我们也可以在Python提示信息(Python prompt)直接输入ch11_1.py程序所建的函数启动与执行。下列是在Python提示信息输入greeting( )函数的实例。
11-2 函数的参数设计
11-1节的程序实例没有传递任何参数,在真实的函数设计与应用中大多是需要传递一些参数的。例如,在前面章节当我们调用Python内置函数时,如len( )、print( )等,皆需要输入参数,接下来将讲解这方面的应用与设计。
11-2-1 传递一个参数
程序实例ch11_3.py:函数内有参数的应用。
# ch11_3.py
def greeting(name):
"""Python函数需传递名字name"""
print("Hi,", name, "Good Morning!")
greeting('Nelson')
执行结果
Hi, Nelson Good Morning!
上述执行时,第5行调用函数greeting( )时,所放的参数是Nelson,这个字符串将传给函数括号内的name参数,所以程序第4行会将Nelson字符串透过name参数打印出来。
在Python应用中,有时候也常会将第4行写成下列语法,可参考ch11_3_1.py,执行结果是相同的。
# ch11_3_1.py
def greeting(name):
"""Python函数需传递名字name"""
print("Hi, " + name + " Good Morning!")
greeting('Nelson')
特别留意由于我们可以在Python Shell环境调用函数,所以在设计与使用者(user)交流的程序时,也可以先省略第5行的调用,让调用留到Python提示信息(Python prompt)环境。
程序实例ch11_4.py:程序设计时不做调用,在Python提示信息环境调用。
# ch11_4.py
def greeting(name):
"""Python函数需传递名字name"""
print("Hi, " + name + " Good Morning!")
执行结果
上述程序最大的特色是greeting(‘Nelson’)与greeting(‘Tina’),皆是从Python提示信息环境做输入。
11-2-2 多个参数传递
当所设计的函数需要传递多个参数,调用此函数时就需要特别留意传递参数的位置需要正确,最后才可以获得正确的结果。最常见的传递参数是数值或字符串数据,有时也会需要传递列表、元组或字典。
程序实例ch11_5.py:设计减法的函数subtract( ),第一个参数会减去第二个参数,然后列出执行结果。
# ch11_5.py
def subtract(x1, x2):
""" 减法设计 """
result = x1 - x2
print(result) # 输出减法结果
print("本程序会执行 a - b 的运算")
a = int(input("a = "))
b = int(input("b = "))
print("a - b = ", end="") # 输出a-b字符串,接下来输出不跳行
subtract(a, b)
执行结果
a = 2
b = 6
a - b = -4
上述函数功能是减法运算,所以需要传递2个参数,然后执行第一个数值减去第2个数值。调用这类的函数时,就必须留意参数的位置,否则会有错误信息产生。对于上述程序而言,变量a和b皆是从屏幕输入,执行第10行调用subtract( )函数时,a将传给x1,b将传给x2。
程序实例ch11_6.py:这也是一个需传递2个参数的实例,第一个是兴趣(interest),第二个是主题(subject)。
# ch11_6.py
def interest(interest_type, subject):
""" 显示兴趣和主题 """
print("我的兴趣是 " + interest_type )
print("在 " + interest_type + " 中, 最喜欢的是 " + subject)
print( )
interest('旅游', '敦煌')
interest('程序设计', 'Python')
执行结果
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 敦煌
我的兴趣是 程序设计
在 程序设计 中, 最喜欢的是 Python
上述程序第8行调用interest( )时,‘旅游’会传给interest_type、‘敦煌’会传给subject。第9行调用interest( )时,‘程序设计’会传给interest_type、‘Python’会传给subject。对于上述的实例,相信读者应该了解调用需要传递多个参数的函数时,所传递参数的位置很重要否则会有不可预期的错误。如下列所示:
11-2-3 关键词参数 参数名称=值
所谓的关键词参数(keyword arguments)是指调用函数时,参数是用参数名称=值配对方式呈现。Python也允许在调用需传递多个参数的函数时,直接将参数名称=值用配对方式传送,这个时候参数的位置就不重要了。
程序实例ch11_7.py:这个程序基本上是重新设计ch11_6.py,但是传递参数时,其中一个参数直接用参数名称=值配对方式传送。
# ch11_7.py
def interest(interest_type, subject):
""" 显示兴趣和主题 """
print("我的兴趣是 " + interest_type )
print("在 " + interest_type + " 中, 最喜欢的是 " + subject)
print( )
interest(interest_type = '旅游', subject = '敦煌') # 位置正确
interest(subject = '敦煌', interest_type = '旅游') # 位置更动
执行结果
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 敦煌
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 敦煌
读者可以留意程序第8行和第9行的“interest_type = ‘旅游’”,当调用函数用配对方式传送参数时,即使参数位置错误,程序执行结果也会相同,因为在调用时已经明确指出所传递的值是要给哪一个参数了。
11-2-4 参数默认值的处理
在设计函数时也可以给参数默认值,如果调用的这个函数没有给参数值,函数的默认值将派上用场。特别需留意:函数设计时含有默认值的参数,必须放置在参数列的最右边。请参考下列程序第2行,如果将“subject = ‘敦煌’”与“interest_type”位置对调,程序会有错误产生。
程序实例ch11_8.py:重新设计ch11_7.py,这个程序会将subject的默认值设为“敦煌”。程序将用不同方式调用,读者可以从中体会程序参数默认值的意义。
# ch11_8.py
def interest(interest_type, subject = '敦煌'):
""" 显示兴趣和主题 """
print("我的兴趣是 " + interest_type )
print("在 " + interest_type + " 中, 最喜欢的是 " + subject)
print( )
interest('旅游') # 传递一个参数
interest(interest_type = '旅游') # 传递一个参数
interest('旅游', '张家界') # 传递二个参数
interest(interest_type = '旅游', subject = '张家界') # 传递二个参数
interest(subject = '张家界', interest_type = '旅游') # 传递二个参数
interest('阅读', '旅游类') # 传递二个参数,不同的主题
执行结果
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 敦煌
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 敦煌
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 张家界
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 张家界
我的兴趣是 旅游
在 旅游 中, 最喜欢的是 张家界
我的兴趣是 阅读
在 阅读 中, 最喜欢的是 旅游类
上述程序第8行和9行只传递一个参数,所以subject就会使用默认值“敦煌”,第10、11和12行传送了2个参数,其中第11和12行笔者用参数名称=值用配对方式调用传送,可以获得一样的结果。第13行主要说明使用不同类的参数一样可以获得正确语意的结果。
11-3 函数返回值
在前面的章节实例我们有执行调用许多内置的函数,有时会返回一些有意义的数据,例如:len( )返回元素数量。有些没有返回值,此时Python会自动返回None,例如:clear( )。为何会如此?本节会完整解说函数返回值的知识。
11-3-1 返回None
前2个小节所设计的函数全部没有“return [返回值]”,Python在直译时会自动返回处理成“return None”,相当于返回None。在一些程序语言,例如,C语言这个None就是NULL,None在Python中独立成为一个数据类型NoneType,下列是实例观察。
程序实例ch11_9.py:重新设计ch11_3.py,这个程序并没有做返回值设计,不过笔者将列出Python返回greeting( )函数的数据是否是None,同时列出返回值的数据类型。
# ch11_9.py
def greeting(name):
"""Python函数需传递名字name"""
print("Hi, ", name, " Good Morning!")
ret_value = greeting('Nelson')
print("greeting( )传回值 = ", ret_value)
print(ret_value, " 的 type = ", type(ret_value))
执行结果
Hi, Nelson Good Morning!
greeting( )传回值 = None
None 的 type = <class 'NoneType'>
上述函数greeting( )没有return,Python将自动处理成return None。其实即使函数设计时有return但是没有返回值,Python也将自动处理成return None,可参考下列实例第5行。
程序实例ch11_10.py:重新设计ch11_9.py,函数末端增加return。
# ch11_10.py
def greeting(name):
"""Python函数需传递名字name"""
print("Hi, ", name, " Good Morning!")
return # Python将自动回传None
ret_value = greeting('Nelson')
print("greeting( )传回值 = ", ret_value)
print(ret_value, " 的 type = ", type(ret_value))
执行结果 与ch11_9.py相同。
11-3-2 简单返回数值数据
参数具有返回值功能,将可以大大增加程序的可读性,返回的基本方式可参考下列程序第5行:
return result # result就是返回的值
程序实例ch11_11.py:利用函数的返回值,重新设计ch11_5.py减法的运算。
# ch11_11.py
def subtract(x1, x2):
""" 减法设计 """
result = x1 - x2
return result # 回传减法结果
print("本程序会执行 a - b 的运算")
a = int(input("a = "))
b = int(input("b = "))
print("a - b = ", subtract(a, b)) # 输出a-b字符串和结果
执行结果
a = 7
b = 13
a - b = -6
一个程序常常是由许多函数所组成,下列是程序含2个函数的应用。
程序实例ch11_12.py:设计加法和减法器。
# ch11_12.py
def subtract(x1, x2):
""" 减法设计 """
return x1 - x2 # 回传减法结果
def addition(x1, x2):
""" 加法设计 """
return x1 + x2 # 回传加法结果
# 使用者输入
print("请输入运算")
print("1:加法")
print("2:减法")
op = int(input("输入1/2: "))
a = int(input("a = "))
b = int(input("b = "))
# 程序运算
if op == 1:
print("a + b = ", addition(a, b)) # 输出a-b字符串和结果
elif op == 2:
print("a - b = ", subtract(a, b)) # 输出a-b字符串和结果
else:
print("运算方法输入错误")
执行结果
请输入运算
1:加法
2:减法
输入1/2: 1
a = 13
b = 22
a + b = 35
11-3-3 返回多个数据的应用
使用return返回函数数据时,也允许返回多个数据,各个数据间只要以逗号隔开即可,读者可参考下列实例第8行。
程序实例ch11_13.py:请输入2个数据,此函数将返回加法、减法、乘法、除法的执行结果。
# ch11_13.py
def mutifunction(x1, x2):
""" 加, 减, 乘, 除四则运算 """
addresult = x1 + x2
subresult = x1 - x2
mulresult = x1 * x2
divresult = x1 / x2
return addresult, subresult, mulresult, divresult
x1 = x2 = 10
add, sub, mul, div = mutifunction(x1, x2)
print("加法结果 = ", add)
print("减法结果 = ", sub)
print("乘法结果 = ", mul)
print("除法结果 = ", div)
执行结果
加法结果 = 20
减法结果 = 0
乘法结果 = 100
除法结果 = 1.0
11-3-4 简单返回字符串数据
返回字符串的方法与11-3-2节返回数值的方法相同。
程序实例ch11_14.py:一般中文姓名是3个字,笔者将中文姓名拆解为第一个字是姓lastname,第二个字是中间名middlename,第三个字是名firstname。这个程序内有一个函数guest_info( ),参数意义分别是名firstname、中间名middlename和姓lastname,以及性别gender组织起来,同时加上问候语返回。
# ch11_14.py
def guest_info(firstname, middlename, lastname, gender):
""" 整合客户名字数据 """
if gender == "M":
welcome = lastname + middlename + firstname + '先生欢迎你'
else:
welcome = lastname + middlename + firstname + '小姐欢迎妳'
return welcome
info1 = guest_info('宇', '星', '洪', 'M')
info2 = guest_info('雨', '冰', '洪', 'F')
print(info1)
print(info2)
执行结果
洪星宇先生欢迎你
洪冰雨小姐欢迎妳
如果读者是处理外国人的名字,则需在lastname、middlename和firstname之间加上空格,同时外国人名字处理方式顺序是firstname middlename lastname,这将是各位的习题。
11-3-5 再谈参数默认值
虽然大多数国人的名字是由3个字所组成,但是偶尔也会遇上2个字的状况,例如,著名影星刘涛。其实外国人的名字中,有些人也是只有2个字,因为没有中间名middlename。如果要让ch11_14.py更完美,可以在函数设计时将middlename默认为空字符串,这样就可以处理没有中间名的问题,参考ch11_8.py可知,设计时必须将默认为空字符串的参数放函数参数列的最右边。
程序实例ch11_15.py:重新设计ch11_14.py,这个程序会将middlename默认为空字符串,这样就可以处理没有中间名middlename的问题,请留意函数设计时需将此参数预设放在最右边,可以参考第2行。
# ch11_15.py
def guest_info(firstname, lastname, gender, middlename = ''):
""" 整合客户名字数据 """
if gender == "M":
welcome = lastname + middlename + firstname + '先生欢迎你'
else:
welcome = lastname + middlename + firstname + '小姐欢迎妳'
return welcome
info1 = guest_info('涛', '刘', 'M')
info2 = guest_info('雨', '洪', 'F', '冰')
print(info1)
print(info2)
执行结果
刘涛先生欢迎你
洪冰雨小姐欢迎妳
上述第10行调用guest_info( )函数时只有3个参数,middlename就会使用默认的空字符串。第11行调用guest_info( )函数时有4个参数,middlename就会使用调用函数时所设的字符串‘冰’。
11-3-6 函数返回字典数据
函数除了可以返回数值或字符串数据外,也可以返回比较复杂的数据,例如,字典或列表等。
程序实例ch11_16.py:这个程序会调用build_vip函数,在调用时会输入VIP_ID编号和Name姓名数据,函数将返回所建立的字典数据。
# ch11_16.py
def build_vip(id, name):
""" 建立VIP信息 """
vip_dict = {'VIP_ID':id, 'Name':name}
return vip_dict
member = build_vip('101', 'Nelson')
print(member)
执行结果
{'VIP_ID': '101', 'Name': 'Nelson'}
上述字典数据只是一个简单的应用,在真正的企业建立VIP数据的案例中,可能还需要性别、电话号码、年龄、电子邮件、地址等信息。在建立VIP数据过程,也许有些人会乐意提供手机号码,有些人不乐意提供,函数设计时我们也可以将Tel电话号码默认为空字符串,但是如果有提供电话号码时,程序也可以将它纳入字典内容。
程序实例ch11_17.py:扩充ch11_16.py,增加电话号码,调用时若没有提供电话号码则字典不含此字段,调用时若有提供电话号码则字典含此字段。
# ch11_17.py
def build_vip(id, name, tel = ''):
""" 建立VIP信息 """
vip_dict = {'VIP_ID':id, 'Name':name}
if tel:
vip_dict['Tel'] = tel
return vip_dict
member1 = build_vip('101', 'Nelson')
member2 = build_vip('102', 'Henry', '0952222333')
print(member1)
print(member2)
执行结果
{'VIP_ID': '101', 'Name': 'Nelson'}
{'VIP_ID': '102', 'Name': 'Henry', 'Tel': '0952222333'}
程序第10行调用build_vip( )函数时,由于有提供电话号码字段,所以上述程序第5行会得到if叙述的tel是True,所以在第6行会将此字段增加到字典中。
11-3-7 将循环应用在建立VIP会员字典
我们可以将循环的观念应用在VIP会员字典的建立。
程序实例ch11_18.py:这个程序在执行时基本上是用无限循环的观念,但是当一个数据建立完成时,会询问是否继续,如果输入非‘y’字符,程序将执行结束。
# ch11_18.py
def build_vip(id, name, tel = ''):
""" 建立VIP信息 """
vip_dict = {'VIP_ID':id, 'Name':name}
if tel:
vip_dict['Tel'] = tel
return vip_dict
while True:
print("建立VIP信息系统")
idnum = input("请输入ID: ")
name = input("请输入姓名: ")
tel = input("请输入电话号码: ") # 如果直接按Enter可不建立此字段
member = build_vip(idnum, name, tel) # 建立字典
print(member, '\n')
repeat = input("是否继续(y/n)? 输入非y字符可结束系统: ")
if repeat != 'y':
break
print("欢迎下次再使用")
执行结果
建立VIP信息系统
请输入ID: 12888
请输入姓名: 晓波
请输入电话号码: 13333333333
{'VIP_ID': '12888', 'Name': '晓波', 'Tel': '13333333333'}
是否继续(y/n)? 输入非y字符可结束系统: y
建立VIP信息系统
请输入ID:
笔者在上述输入第2个数据时,在电话号码字段没有输入直接单击Enter键,这个动作相当于不做输入,此时将造成可以省略此字段。
11-4 调用函数时参数是列表
11-4-1 基本传递列表参数的应用
在调用函数时,也可以将列表(此列表可以是由数值、字符串或字典所组成)当参数传递给函数,然后函数可以遍历列表内容,然后执行更进一步的运作。
程序实例ch11_19:传递列表给product_msg( )函数,函数会遍历列表,然后列出一封产品发表会的信件。
# ch11_19
def product_msg(customers):
str1 = '亲爱的: '
str2 = '本公司将在2020年12月20日北京举行产品发表会'
str3 = '总经理:深石敬上'
for customer in customers:
msg = str1 + customer + '\n' + str2 + '\n' + str3
print(msg, '\n')
members = ['Damon', 'Peter', 'Mary']
product_msg(members)
执行结果
亲爱的: Damon
本公司将在2020年12月20日北京举行产品发表会
总经理:深石敬上
亲爱的: Peter
本公司将在2020年12月20日北京举行产品发表会
总经理:深石敬上
亲爱的: Mary
本公司将在2020年12月20日北京举行产品发表会
总经理:深石敬上
11-4-2 在函数内修订列表的内容
Python允许在函数内直接修订列表的内容,同时列表经过修正后,主程序的列表也将随之永久性更改结果。
程序实例ch11_20.py:设计一个麦当劳的点餐系统,顾客在麦当劳点餐时,可以将所点的餐点放入unserved列表,服务完成后将已服务餐点放入served列表。
# ch11_20.py
def kitchen(unserved, served):
""" 将未服务的餐点转为已经服务 """
print("厨房处理顾客所点的餐点")
while unserved:
current_meal = unserved.pop( )
# 模拟出餐点过程
print("菜单: ", current_meal)
# 将已出餐点转入已经服务列表
served.append(current_meal)
def show_unserved_meal(unserved):
""" 显示尚未服务的餐点 """
print("=== 下列是尚未服务的餐点 ===")
if not unserved:
print("*** 没有餐点 ***", "\n")
for unserved_meal in unserved:
print(unserved_meal)
def show_served_meal(served):
""" 显示已经服务的餐点 """
print("=== 下列是已经服务的餐点 ===")
if not served:
print("*** 没有餐点 ***", "\n")
for served_meal in served:
print(served_meal)
unserved = ['大麦克', '劲辣鸡腿堡', '麦克鸡块'] # 所点餐点
served = [] # 已服务餐点
# 列出餐厅处理前的点餐内容
show_unserved_meal(unserved) # 列出未服务餐点
show_served_meal(served) # 列出已服务餐点
# 餐厅服务过程
kitchen(unserved, served) # 餐厅处理过程
print("\n", "=== 厨房处理结束 ===", "\n")
# 列出餐厅处理后的点餐内容
show_unserved_meal(unserved) # 列出未服务餐点
show_served_meal(served) # 列出已服务餐点
执行结果
=== 下列是尚未服务的餐点 ===
大麦克
劲辣鸡腿堡
麦克鸡块
=== 下列是已经服务的餐点 ===
*** 没有餐点 ***
厨房处理顾客所点的餐点
菜单: 麦克鸡块
菜单: 劲辣鸡腿堡
菜单: 大麦克
=== 厨房处理结束 ===
=== 下列是尚未服务的餐点 ===
*** 没有餐点 ***
=== 下列是已经服务的餐点 ===
麦克鸡块
劲辣鸡腿堡
大麦克
这个程序的主程序从第28行开始,基本上将所点的餐点放unserved列表,第29行将已经处理的餐点放在served列表,程序刚开始是设定空列表。为了了解所做的设定,所以第32和33行是列出尚未服务的餐点和已经服务的餐点。
程序第36行是调用kitchen( )函数,这个程序主要是列出餐点,同时将已经处理的餐点从尚未服务列表unserved,转入已经服务的列表served。
程序第40和41行执行一次列出尚未服务餐点和已经服务餐点,以便验证整个执行过程。
对于上述程序而言,读者可能会好奇,主程序部分与函数部分是使用相同的列表变量served与unserved,所以经过第36行调用kitchen( )后造成列表内容的改变,是否设计这类欲更改列表内容的程序,函数与主程序的变量名称一定要相同?答案是否定的。其实这牵涉到全局变量(glocal variable)与局部变量(local variable)的观念,将在11-7节说明。
程序实例ch11_21.py:重新设计ch11_20.py,但是主程序的尚未服务列表改为order_list,已经服务列表改为served_list,下列只列出主程序内容。
# ch11_21.py
def kitchen(unserved, served):
""" 将未服务的餐点转为已经服务 """
print("厨房处理顾客所点的餐点")
while unserved:
current_meal = unserved.pop( )
# 模拟出餐点过程
print("菜单: ", current_meal)
# 将已出餐点转入已经服务列表
served.append(current_meal)
def show_unserved_meal(unserved):
""" 显示尚未服务的餐点 """
print("=== 下列是尚未服务的餐点 ===")
if not unserved:
print("*** 没有餐点 ***", "\n")
for unserved_meal in unserved:
print(unserved_meal)
def show_served_meal(served):
""" 显示已经服务的餐点 """
print("=== 下列是已经服务的餐点 ===")
if not served:
print("*** 没有餐点 ***", "\n")
for served_meal in served:
print(served_meal)
order_list = ['大麦克', '劲辣鸡腿堡', '麦克鸡块'] # 所点餐点
served_list = [] # 已服务餐点
# 列出餐厅处理前的点餐内容
show_unserved_meal(order_list) # 列出未服务餐点
show_served_meal(served_list) # 列出已服务餐点
# 餐厅服务过程
kitchen(order_list, served_list) # 餐厅处理过程
print("\n", "=== 厨房处理结束 ===", "\n")
# 列出餐厅处理后的点餐内容
show_unserved_meal(order_list) # 列出未服务餐点
show_served_meal(served_list) # 列出已服务餐点
执行结果 与ch11_20.py相同。
上述结果最主要原因是,当传递列表给函数时,即使函数内的列表与主程序列表是不同的名称,但是函数列表unserved/served与主程序列表order_list/served_list是指向相同的内存位置,所以在函数更改列表内容时主程序列表内容也随着更改。
11-4-3 使用副本传递列表
有时候设计餐厅系统时,可能想要保存餐点内容,但是经过先前程序设计可以发现order_list列表已经变为空列表了,为了避免这样的情形发生,可以在调用kitchen( )函数时传递副本列表,处理方式如下:
kitchen(order_list[:], served_list) # 传递副本列表
程序实例ch11_22.py:重新设计ch11_21.py,但是保留原order_list的内容,整个程序主要是在第36行,笔者使用副本传递列表,其他只是程序语意批注有一些小调整,例如,原先函数show_unserved_meal( )改名为show_order_meal( )。
# ch11_22.py
def kitchen(unserved, served):
""" 将所点的餐点转为已经服务 """
print("厨房处理顾客所点的餐点")
while unserved:
current_meal = unserved.pop( )
# 模拟出餐点过程
print("菜单: ", current_meal)
# 将已出餐点转入已经服务列表
served.append(current_meal)
def show_order_meal(unserved):
""" 显示所点的餐点 """
print("=== 下列是所点的餐点 ===")
if not unserved:
print("*** 没有餐点 ***", "\n")
for unserved_meal in unserved:
print(unserved_meal)
def show_served_meal(served):
""" 显示已经服务的餐点 """
print("=== 下列是已经服务的餐点 ===")
if not served:
print("*** 没有餐点 ***", "\n")
for served_meal in served:
print(served_meal)
order_list = ['大麦克', '劲辣鸡腿堡', '麦克鸡块'] # 所点餐点
served_list = [] # 已服务餐点
# 列出餐厅处理前的点餐内容
show_order_meal(order_list) # 列出所点的餐点
show_served_meal(served_list) # 列出已服务餐点
# 餐厅服务过程
kitchen(order_list[:], served_list) # 餐厅处理过程
print("\n", "=== 厨房处理结束 ===", "\n")
# 列出餐厅处理后的点餐内容
show_order_meal(order_list) # 列出所点的餐点
show_served_meal(served_list) # 列出已服务餐点
执行结果
=== 下列是所点的餐点 ===
大麦克
劲辣鸡腿堡
麦克鸡块
=== 下列是已经服务的餐点 ===
*** 没有餐点 ***
厨房处理顾客所点的餐点
菜单: 麦克鸡块
菜单: 劲辣鸡腿堡
菜单: 大麦克
=== 厨房处理结束 ===
=== 下列是所点的餐点 ===
大麦克
劲辣鸡腿堡
麦克鸡块
=== 下列是已经服务的餐点 ===
麦克鸡块
劲辣鸡腿堡
大麦克
由上述执行结果可以发现,原先存储点餐的order_list列表经过kitchen( )函数后,此列表的内容没有改变。
11-5 传递任意数量的参数
11-5-1 基本传递处理任意数量的参数
在设计Python的函数时,有时候可能会碰上不知道会有多少个参数会传递到这个函数,此时可以用下列方式设计。
程序实例ch11_23.py:建立一个冰淇淋的配料程序,一般冰淇淋可以在上面加上配料,这个程序在调用制作冰淇淋函数make_icecream( )时,可以传递0到多个配料,然后make_icecream( )函数会将配料结果的冰淇淋列出来。
# ch11_23.py
def make_icecream(*toppings):
# 列出制作冰淇淋的配料
print("这个冰淇淋所加配料如下")
for topping in toppings:
print("--- ", topping)
make_icecream('草莓酱')
make_icecream('草莓酱', '葡萄干', '巧克力碎片')
执行结果
这个冰淇淋所加配料如下
--- 草莓酱
这个冰淇淋所加配料如下
--- 草莓酱
--- 葡萄干
--- 巧克力碎片
上述程序最关键的是第2行make_icecream( )函数的参数“toppings”,这个加上“”符号的参数代表可以有1到多个参数将传递到这个函数内。
11-5-2 设计含有一般参数与任意数量参数的函数
程序设计时有时会遇上需要传递一般参数与任意数量参数,碰上这类状况,任意数量的参数必须放在最右边。
程序实例ch11_24.py:重新设计ch11_23.py,传递参数时第一个参数是冰淇淋的种类,然后才是不同数量的冰淇淋的配料。
# ch11_24.py
def make_icecream(icecream_type, *toppings):
# 列出制作冰淇淋的配料
print("这个 ", icecream_type, " 冰淇淋所加配料如下")
for topping in toppings:
print("--- ", topping)
make_icecream('香草', '草莓酱')
make_icecream('芒果', '草莓酱', '葡萄干', '巧克力碎片')
执行结果
这个 香草 冰淇淋所加配料如下
--- 草莓酱
这个 芒果 冰淇淋所加配料如下
--- 草莓酱
--- 葡萄干
--- 巧克力碎片
11-5-3 设计含有一般参数与任意数量的关键词参数
在11-2-3节笔者有介绍函数的参数是关键词参数,其实我们也可以设计含任意数量关键词参数的函数。
程序实例ch11_25.py:这个程序基本上是用build_dict( )函数建立一个球员的字典数据,主程序会传入一般参数与任意数量的关键词参数,最后可以列出执行结果。
# ch11_25.py
def build_dict(name, age, **players):
# 建立NBA球员的字典数据
info = {} # 建立空字典
info['Name'] = name
info['Age'] = age
for key, value in players.items( ):
info[key] = value
return info # 回传所建的字典
player_dict = build_dict('James', '32',
City = 'Cleveland',
State = 'Ohio')
print(player_dict) # 打印所建字典
执行结果
{'Name': 'James', 'Age': '32', 'City': 'Cleveland', 'State': 'Ohio'}
上述最关键的是第2行build_dict( )函数内的参数“**player”,这表示可以接受任意数量关键词参数。
11-6 递归式函数设计recursive
一个函数可以调用其他函数也可以调用自己,其中调用本身的动作称递归式(recursive)调用,递归式调用有下列特色:
- 每次调用自己时,都会使范围越来越小。
- 必须要有一个终止的条件来结束递归函数。
递归函数可以使程序变得很简洁,但是设计这类程序一不小心就很容易掉入无限循环的陷阱,所以使用这类函数时一定要特别小心。递归函数最常见的应用是处理正整数的阶乘(factorial),一个正整数的阶乘是所有小于以及等于该数的正整数的积,同时如果正整数是0则阶乘为1,依照观念正整数是1时阶乘也是1。此阶乘数字的表示法为n!。
实例1:n是3,下列是阶乘数的计算方式。
n! = 1 * 2 * 3
结果是6。
实例2:n是5,下列是阶乘数的计算方式。
n! = 1 * 2 * 3 * 4 * 5
结果是120。
阶乘数观念是由法国数学家克里斯蒂安·克兰普(Christian Kramp, 1760-1826)所发表,他学医但同时对数学感兴趣,发表许多数学文章。
程序实例ch11_26.py:使用递归函数执行阶乘(factorial)运算。
# ch11_26.py
def factorial(n):
# 计算n的阶乘, n 必须是正整数
if n == 1:
return 1
else:
return (n * factorial(n-1))
value = 3
print(value, " 的阶乘结果是 = ", factorial(value))
value = 5
print(value, " 的阶乘结果是 = ", factorial(value))
执行结果
3 的阶乘结果是 = 6
5 的阶乘结果是 = 120
上述factorial( )函数的终止条件是参数值为1的情况,由第4行判断然后返回1,下列是正整数为3时递归函数的情况解说。
11-7 局部变量与全局变量
在设计函数时,另一个重点是适当地使用变量名称,某个变量只有在该函数内使用,影响范围限定在这个函数内,这个变量称局部变量(local variable)。如果某个变量的影响范围是在整个程序,则这个变量称全局变量(global variable)。
Python程序在调用函数时会建立一个内存工作区间,在这个内存工作区间可以处理属于这个函数的变量,当函数工作结束,返回原先调用程序时,这个内存工作区间就被收回,原先存在的变量也将被销毁,这也是为何局部变量的影响范围只限定在所属的函数内。
对于全局变量而言,一般是在主程序内建立,程序在执行时,不仅主程序可以引用,所有属于这个程序的函数也可以引用,所以它的影响范围是整个程序。
11-7-1 全局变量可以在所有函数使用
一般在主程序内建立的变量称全局变量,这个变量程序内与本程序的所有函数皆可以引用。
程序实例ch11_27.py:这个程序会设定一个全局变量,然后函数也可以调用引用。
# ch11_27.py
def printmsg( ):
# 函数本身没有定义变量, 只有执行打印全局变量功能
print("函数打印: ", msg) # 打印全局变量
msg = 'Global Variable' # 设定全局变量
print("主程序行印: ", msg) # 打印全局变量
printmsg( ) # 呼叫函数
执行结果
主程序行印: Global Variable
函数打印: Global Variable
11-7-2 局部变量与全局变量使用相同的名称
在程序设计时建议全局变量与函数内的局部变量不要使用相同的名称,因为很容易造成混淆。如果全局变量与函数内的局部变量使用相同的名称,Python会将相同名称的区域与全局变量视为不同的变量,在局部变量所在的函数是使用局部变量内容,其他区域则是使用全局变量的内容。
程序实例ch11_28.py:局部变量与全局变量定义了相同的变量msg,但是内容不相同。然后执行打印,可以发现在函数与主程序所打印的内容有不同的结果。
# ch11_28.py
def printmsg( ):
# 函数本身有定义变量, 将执行打印局部变量功能
msg = 'Local Variable' # 设定局部变量
print("函数打印: ", msg) # 打印局部变量
msg = 'Global Variable' # 这是全局变量
print("主程序行印: ", msg) # 打印全局变量
printmsg( ) # 呼叫函数
执行结果
主程序行印: Global Variable
函数打印: Local Variable
11-7-3 程序设计需注意事项
一般程序设计时有关使用局部变量需注意下列事项,否则程序会有错误产生。
局部变量内容无法在其他函数引用。
局部变量内容无法在主程序引用。
程序实例ch11_29.py:局部变量在其他函数引用,造成程序错误的应用。
# ch11_29.py
def defmsg( ):
msg = 'pringmsg variable'
def printmsg( ):
print(msg) # 打印defmsg( )函数定义的局部变量
printmsg( ) # 呼叫printmsg( )
执行结果
Traceback (most recent call last):
File "e:\桌面Desktop\Python王者归来\代码\ch11\ch11_29.py", line 8, in <module>
printmsg( ) # 呼叫printmsg( )
^^^^^^^^^^^
File "e:\桌面Desktop\Python王者归来\代码\ch11\ch11_29.py", line 6, in printmsg
print(msg) # 打印defmsg( )函数定义的局部变量
^^^
NameError: name 'msg' is not defined
上述程序的错误原因主要是defmsg( )函数内没有定义msg变量,所以产生程序错误。
程序实例ch11_30.py:局部变量在主程序引用产生错误的实例。
# ch11_30.py
def defmsg( ):
msg = 'pringmsg variable'
print(msg) # 主程序行印局部变量产生错误
执行结果
Traceback (most recent call last):
File "e:\桌面Desktop\Python王者归来\代码\ch11\ch11_30.py", line 5, in <module>
print(msg) # 主程序行印局部变量产生错误
^^^
NameError: name 'msg' is not defined
上述程序的错误原因主要是主程序内没有定义msg变量,所以产生程序错误。
11-8 匿名函数lambda
所谓的匿名函数(anonymous function)是指一个没有名称的函数,Python是使用def定义一般函数,匿名函数则是使用lambda来定义,有的人称之为lambda表达式,也可以将匿名函数称lambda函数。通常会将匿名函数与Python的内置函数filter( )、map( )等共同使用,此时匿名函数将只是这些函数的参数,笔者未来将以实例做解说。
11-8-1 匿名函数lambda的语法
匿名函数最大特色是可以有许多的参数,但是只能有一个程序码表达式,然后可以将执行结果返回。
lambda arg1[, arg2, …, argn]:expression # arg1是参数,可以有多个参数
程序实例ch11_31.py:这是单一参数的匿名函数应用,可以返回平方值。
# ch11_31.py
# 定义lambda函数
square = lambda x: x ** 2
# 输出平方值
print(square(10))
执行结果
100
读者可以留意第6行调用匿名函数方式,其实上述匿名函数可以用一般函数取代。
程序实例ch11_32.py:使用一般函数取代匿名函数,重新设计ch11_31.py。
# ch11_32.py
# 定义lambda函数
product = lambda x, y: x * y
# 输出变数的积
print(product(5, 10))
执行结果 与ch11_31.py相同。
下列是匿名函数含有多个参数的应用。
程序实例ch11_33.py:含2个参数的匿名函数应用,可以返回参数的积(相乘的结果)。
# ch11_33.py
# 定义lambda函数
product = lambda x, y: x * y
# 输出相乘结果
print(product(5, 10))
执行结果
50
11-8-2 匿名函数使用与filter( )
匿名函数一般是用在不需要函数名称的场合,例如,一些高阶函数(higher-order function)的参数可能是函数,这时就很适合使用匿名函数,同时让程序变得更简洁。有一个内置函数filter( ),它的语法格式如下:
filter(function, iterable)
上述函数将依次对iterable(可以重复执行,例如,字符串string、列表list或元组tuple)的元素(item)放入function(item)内,然后将function( )函数执行结果是True的元素(item)组成新的筛选对象(filter object)返回。
程序实例ch11_34.py:使用传统函数定义方式将列表元素内容是奇数的元素筛选出来。
# ch11_34.py
def oddfn(x):
return x if (x % 2 == 1) else None
mylist = [5, 10, 15, 20, 25, 30]
filter_object = filter(oddfn, mylist) # 传回filter object
# 输出奇数列表
print("奇数列表: ",[item for item in filter_object])
执行结果
奇数列表: [5, 15, 25]
上述第9行笔者使用item for item infilter_object,这是可以取得filter object元素的方式,这个操作方式与下列for循环类似。
for item infilter_object:
print(item)
若是想要获得列表结果,可以使用下列方式:
oddlist = [item for item infilter_object]
程序实例ch11_35.py:重新设计ch11_34.py,将filter object转为列表,下列只列出与ch11_34.py不同的程序代码。
# ch11_35.py
def oddfn(x):
return x if (x % 2 == 1) else None
mylist = [5, 10, 15, 20, 25, 30]
filter_object = filter(oddfn, mylist) # 传回filter object
oddlist = [item for item in filter_object]
# 输出奇数列表
print("奇数列表: ",oddlist)
执行结果 与ch11_34.py相同。
匿名函数的最大优点是可以让程序变得更简洁,可参考下列程序实例。
程序实例ch11_36.py:使用匿名函数重新设计ch11_35.py。
# ch11_36.py
mylist = [5, 10, 15, 20, 25, 30]
oddlist = list(filter(lambda x: (x % 2 == 1), mylist))
# 输出奇数列表
print("奇数列表: ",oddlist)
执行结果 与ch11_35.py相同。
上述程序第4行笔者直接使用list( )函数将返回的filter object转成列表了。
11-8-3 匿名函数使用与map( )
有一个内置函数map( ),它的语法格式如下:
map(function, iterable)
上述函数将依次对iterable(可以重复执行,例如,字符串string、列表list或元组tuple)的元素(item)放入function(item)内,然后将function( )函数执行结果组成新的筛选对象(filter object)返回。
程序实例ch11_37.py:使用匿名函数对列表元素执行计算平方运算。
# ch11_37.py
mylist = [5, 10, 15, 20, 25, 30]
squarelist = list(map(lambda x: x ** 2, mylist))
# 输出列表元素的平方值
print("列表的平方值: ",squarelist)
执行结果
列表的平方值: [25, 100, 225, 400, 625, 900]
11-9 pass与函数
在7-4-6节已经有对pass指令做介绍,其实当我们在设计大型程序时,可能会先规划各个函数的功能,然后逐一完成各个函数设计,但是在程序完成前我们可以先将尚未完成的函数内容放上pass。
程序实例ch11_38.py:将pass应用在函数设计。
# ch11_38.py
def fun(arg):
pass
执行结果 程序没有执行结果。
11-10 type关键词应用在函数
在结束本章前笔者列出函数的数据类型,读者可以参考。
程序实例ch11_39.py:输出函数与匿名函数的数据类型。
# ch11_39.py
def fun(arg):
pass
print("列出fun的type类型 : ", type(fun))
print("列出lambda的type类型: ", type(lambda x:x))
print("列出内建函数abs的type类型: ", type(abs))
执行结果
列出fun的type类型 : <class 'function'>
列出lambda的type类型: <class 'function'>
列出内建函数abs的type类型: <class 'builtin_function_or_method'>