第五章 函数与lambda表达式
函数是执行特定任务的一段代码,程序通过将一段代码定义成函数,并为该函数指定一个函数名,这样即可在需要的时候多次调用这段代码。因此,函数是代码复用的重要手段。
函数入门
理解函数
将实现特定功能的代码定义成一个函数,每次当程序需要实现该功能时,只要执行(调用)该函数即可。
所谓函数,就是指为一段实现特定功能的代码“取”一个名字,以后即可通过该名字来执行(调用)该函数。
通常,函数可以接收零个或多个参数,也可以返回零个或多个值。
从函数使用者角度看,函数就像一个“黑匣子”,程序将零个或多个参数传入这个“黑匣子”,该“黑匣子”经过一番计算即可返回零个或多个值。
从函数定义者(实现函数的人)的角度来看,其至少需要想清楚以下3点:
- 函数需要几个关键的需要动态变化的数据,这些数据应该被定义成函数的参数。
- 函数需要传出几个重要的数据(就是调用该函数的人希望得到的数据),这些数据应该被定义成返回值。
- 函数的内部实现过程。
定义函数和调用函数
定义函数的语法格式如下:
def 函数名(形参列表):
// 由零条到多条可执行语句组成的函数
[return [返回值]]
python声明函数必须使用def关键字,对函数语法格式的详细说明如下:
- 函数名:从语法角度看,函数名只要是一个合法的标志符即可;从程序的可读性角度看,函数名应该由一个或多个有意义的单词连缀而成,每个单词的字母全部小写,单词与单词之间使用下划线分割。
- 形参列表:用于定义该函数可以接收的形参。形参列表由多个形参名组成,多个形参名之间有英文逗号(,)隔开。一旦在定义函数时指定了形参列表,调用该函数时就必须传入对应的形参值 --- 谁调用,谁负责为形参赋值。
## 定义一个函数,声明2个形参
#def my_max(x, y) :
# # 定义一个变量z,该变量等于x、y中较大的值
# z = x if x > y else y
# # 返回变量z的值
# return z
def my_max(x, y) :
# 返回一个表达式
return x if x > y else y
# 定义一个函数,声明一个形参
def say_hi(name) :
print("===正在执行say_hi()函数===")
return name + ",您好!"
a = 6
b = 9
# 调用my_max()函数,将函数返回值赋值给result变量
result = my_max(a , b) # ①
print("result:", result)
# 调用say_hi()函数,直接输出函数的返回值
print(say_hi("孙悟空")) # ②
输出结果:
result: 9
===正在执行say_hi()函数===
孙悟空,您好!
在函数体中使用return语句可以显示地返回一个值,return语句返回的值既可以是有值的变量,也可是一个表达式。
为函数提供文档
为函数编写说明文档 --- 只要把一段字符串放在函数声明之后、函数体之前,这段字符串将被作为函数的部分,这个文档就是函数的说明文档。
程序即可通过help()函数查看函数的说明文档,也可通过函数的_doc_属性访问函数的说明文档。示例:
def my_max(x, y) :
'''
获取两个数值之间较大数的函数。
my_max(x, y)
返回x、y两个参数之间较大的那个
'''
# 定义一个变量z,该变量等于x、y中较大的值
z = x if x > y else y
# 返回变量z的值
return z
# 使用help()函数查看my_max的帮助文档
help(my_max)
print("====================================")
print(my_max.__doc__)
输出结果:
Help on function my_max in module __main__:
my_max(x, y)
获取两个数值之间较大数的函数。
my_max(x, y)
返回x、y两个参数之间较大的那个
====================================
获取两个数值之间较大数的函数。
my_max(x, y)
返回x、y两个参数之间较大的那个
上面程序使用多行字符串的语法为my_max()函数编写了说明文档,接下来程序即可通过help()函数查看该函数的说明文档,也可通过 _doc _属性访问该函数的说明文档。
多个返回值
如果程序需要有多个返回值,则即可将多个值包装成列表之后返回,也可直接返回多个值。如果直接返回多个值,python会自动将多个值封装成元组。
def sum_and_avg(list):
sum = 0
count = 0
for e in list:
# 如果元素e是数值
if isinstance(e, int) or isinstance(e, float):
count += 1
sum += e
return sum, sum / count
my_list = [20, 15, 2.8, 'a', 35, 5.9, -1.8]
# 获取sum_and_avg函数返回的多个值,多个返回值被封装成元组
tp = sum_and_avg(my_list) #①
print(tp)
# 使用序列解包来获取多个返回值
s, avg = sum_and_avg(my_list) #②
print(s)
print(avg)
输出结果:
(76.9, 12.816666666666668)
76.9
12.816666666666668
递归函数
在一个函数体内调用它自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种循环执行无需循环控制。
当一个函数不断地调用它自身时,必须在某个时刻函数的返回值是确定的,即不再调用它自身;否则,这种递归就变成了无穷递归,类似于死循环。因此,在定义递归函数时有一条最重要的规定:递归一定要向已知方向进行。
总之,只要在一个函数的函数体内调用了函数自身,就是函数递归。递归一定要向已知方向进行。
def fn(n) :
if n == 0 :
return 1
elif n == 1 :
return 4
else :
# 函数中调用它自身,就是函数递归
return 2 * fn(n - 1) + fn(n - 2)
# 输出fn(10)的结果
print("fn(10)的结果是:", fn(10))
输出结果:
fn(10)的结果是: 10497
函数的参数
在定义python函数时可定义形参(形式参数的意思),这些形参的值要等到调用时才能确定下来,由函数的调用者负责为形参传入值。
关键字参数
python函数的参数名不是无意义的,python允许在调用函数时通过名字来传入参数。
按照形参位置传入的参数被称为位置参数。如果使用位置参数的方式来传入参数值,则必须严格按照定义函数时指定的顺序来传入参数值;如果根据参数名来传入参数值,则无需遵守形参定义的顺序。这种方式被称为关键字(keyword)参数。
# 定义一个函数
def girth(width , height):
print(" ", width)
print("height: ", height)
return 2 * (width + height)
# 传统调用函数的方式,根据位置传入参数
print(girth(3.5, 4.8))
# 根据关键字参数来传入参数
print(girth(width = 3.5, height = 4.8))
# 使用关键字参数时可交换位置
print(girth(height = 4.8, width = 3.5))
# 部分使用关键字参数,部分使用位置参数
print(girth(3.5, height = 4.8))
输出结果:
3.5
height: 4.8
16.6
3.5
height: 4.8
16.6
3.5
height: 4.8
16.6
3.5
height: 4.8
16.6
需要说明的是,如果希望在调用函数时混合使用关键字参数和位置参数,则关键字参数必须位于位置参数之后。换句话说,关键字参数之后的只能是关键字参数。
# 定义一个函数
def girth(width , height):
print(" ", width)
print("height: ", height)
return 2 * (width + height)
# 位置参数必须放在关键字参数之前,下面代码错误
print(girth(width = 3.5, 4.8))
输出结果:
File "C:Userszz.spyder-py3 emp.py", line 8
print(girth(width = 3.5, 4.8))
^
SyntaxError: positional argument follows keyword argument
参数默认值
在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值 --- 这样在调用函数时就可以省略为该形参传入参数值,而是直接使用该形参的默认值。为形参指定默认值的语法格式如下:
形参名 = 默认值
形参的默认值紧跟在形参之后,中间以英文“=”隔开。
# 为两个参数指定默认值
def say_hi(name = "孙悟空", message = "欢迎来到疯狂软件"):
print(name, ", 您好")
print("消息是:", message)
# 全部使用默认参数
say_hi()
# 只有message参数使用默认值
say_hi("白骨精")
# 两个参数都不使用默认值
say_hi("白骨精", "欢迎学习Python")
# 只有name参数使用默认值
say_hi(message = "欢迎学习Python")
输出结果:
孙悟空 , 您好
消息是: 欢迎来到疯狂软件
白骨精 , 您好
消息是: 欢迎来到疯狂软件
白骨精 , 您好
消息是: 欢迎学习Python
孙悟空 , 您好
消息是: 欢迎学习Python
如果只传入一个位置参数,由于该参数位于第一位,系统会将该参数传给name参数。
由于python要求在调用函数时关键字参数必须位于位置参数的后面,因此在定义函数时指定了默认值的参数(关键字参数)必须在没有默认值的参数之后。示例:
# 定义一个打印三角形的函数,有默认值的参数必须放在后面
def printTriangle(char, height = 5) :
for i in range(1, height + 1) :
# 先打印一排空格
for j in range(height - i) :
print(' ', end = '')
# 再打印一排特殊字符
for j in range(2 * i - 1) :
print(char, end = '')
print()
printTriangle('@', 6)
printTriangle('#', height=7)
printTriangle(char = '*')
输出结果:
@
@@@
@@@@@
@@@@@@@
@@@@@@@@@
@@@@@@@@@@@
#
###
#####
#######
#########
###########
#############
*
***
*****
*******
*********
参数收集(个数可变的参数)
python允许在形参前面添加一个星号(*),这样就意味着该参数可接收多个参数值,多个参数值被当成元组传入。示例:
# 定义了支持参数收集的函数
def test(a, *books) :
print(books)
# books被当成元组处理
for b in books :
print(b)
# 输出整数变量a的值
print(a)
# 调用test()函数
test(5 , "aaa" , "bbb")
输出结果:
('aaa', 'bbb')
aaa
bbb
5
python允许个数可变的形参可以位于形参列表的任意位置(不要求是形参列表的最后一个参数),但 python要求一个函数最多只能带一个支持“普通”参数收集的形参。示例:
# 定义了支持参数收集的函数
def test(*books ,num) :
print(books)
# books被当成元组处理
for b in books :
print(b)
print(num)
# 调用test()函数
test("疯狂iOS讲义", "疯狂Android讲义", num = 20)
my_list = ["疯狂Swift讲义", "疯狂Python讲义"]
# 将列表的多个元素传给支持参数收集的参数
test(my_list, num = 20)
my_tuple= ("疯狂Swift讲义", "疯狂Python讲义")
# 将元组的多个元素传给支持参数收集的参数
test(*my_tuple, num = 20)
输出结果:
('疯狂iOS讲义', '疯狂Android讲义')
疯狂iOS讲义
疯狂Android讲义
20
(['疯狂Swift讲义', '疯狂Python讲义'],)
['疯狂Swift讲义', '疯狂Python讲义']
20
('疯狂Swift讲义', '疯狂Python讲义')
疯狂Swift讲义
疯狂Python讲义
20
python还可以收集关键字参数,此时Python会将这种关键字参数收集成字典。为了让python能收集关键字参数,需要在参数前面添加两个星号。在这种情况下,一个函数可同时包含一个支持“普通”参数收集的参数和一个支持关键字参数收集的参数。示例:
# 定义了支持参数收集的函数
def test(x, y, z=3, *books, **scores) :
print(x, y, z)
print(books)
print(scores)
test(1, 2, 3, "疯狂iOS讲义" , "疯狂Android讲义", 语文=89, 数学=94)
test(1, 2, "疯狂iOS讲义" , "疯狂Android讲义", 语文=89, 数学=94)
test(1, 2, 语文=89, 数学=94)
输出结果:
1 2 3
('疯狂iOS讲义', '疯狂Android讲义')
{'语文': 89, '数学': 94}
1 2 疯狂iOS讲义
('疯狂Android讲义',)
{'语文': 89, '数学': 94}
1 2 3
()
{'语文': 89, '数学': 94}
逆向参数收集
逆向参数收集,指的是在程序已有列表、元组、字典等对象的前提下,把它们的元素“拆开”后传给函数的参数。
逆向参数收集需要在传入的列表、元组参数之前添加一个星号,在字典参数之前添加两个星号。示例1:
def test(name, message):
print("用户是: ", name)
print("欢迎消息: ", message)
my_list = ['孙悟空', '欢迎来疯狂软件']
test(*my_list)
输出结果:
用户是: 孙悟空
欢迎消息: 欢迎来疯狂软件
示例2:
def foo(name, *nums):
print("name参数: ", name)
print("nums参数: ", nums)
my_tuple = (1, 2, 3)
# 使用逆向收集,将my_tuple元组的元素传给nums参数
foo('fkit', *my_tuple)
输出结果:
name参数: fkit
nums参数: (1, 2, 3)
示例3:
def foo(name, *nums):
print("name参数: ", name)
print("nums参数: ", nums)
my_tuple = (1, 2, 3)
# 使用逆向收集,将my_tuple元组的第一个元素传给name参数,剩下参数传给nums参数
foo(*my_tuple)
输出结果:
name参数: 1
nums参数: (2, 3)
示例4:
def foo(name, *nums):
print("name参数: ", name)
print("nums参数: ", nums)
my_tuple = (1, 2, 3)
# 不使用逆向收集,my_tuple元组整体传给name参数
foo(my_tuple)
输出结果:
name参数: (1, 2, 3)
nums参数: ()
字典也支持逆向收集,字典将会以关键字参数的形式传入,示例:
def bar(book, price, desc):
print(book, " 这本书的价格是: ", price)
print('描述信息', desc)
my_dict = {'price': 89, 'book': '疯狂Python讲义', 'desc': '这是一本系统全面的Python学习图书'}
# 按逆向收集的方式将my_dict的多个key-value传给bar()函数
bar(**my_dict)
输出结果:
疯狂Python讲义 这本书的价格是: 89
描述信息 这是一本系统全面的Python学习图书
函数的参数传递机制
python中函数的参数传递机制都是“值传递”。所谓值传递,就是将实际参数值的副本(复制品)传入函数,而参数本身不会受到任何影响。
def swap(a , b) :
# 下面代码实现a、b变量的值交换
a, b = b, a
print("swap函数里,a的值是",
a, ";b的值是", b)
a = 6
b = 9
swap(a , b)
print("交换结束后,变量a的值是",
a , ";变量b的值是", b)
输出结果:
swap函数里,a的值是 9 ;b的值是 6
交换结束后,变量a的值是 6 ;变量b的值是 9
值传递的实质:当系统开始执行函数时,系统对形参进行初始化,就是把实参变量的值赋给函数的形参变量,在函数中操作的并不是实际的实参变量。
def swap(dw):
# 下面代码实现dw的a、b两个元素的值交换
dw['a'], dw['b'] = dw['b'], dw['a']
print("swap函数里,a元素的值是",
dw['a'], ";b元素的值是", dw['b'])
# 把dw直接赋值为None,让它不再指向任何对象
dw = None
dw = {'a': 6, 'b': 9}
swap(dw)
print("交换结束后,a元素的值是",
dw['a'], ";b元素的值是", dw['b'])
输出结果:
swap函数里,a元素的值是 9 ;b元素的值是 6
交换结束后,a元素的值是 9 ;b元素的值是 6
变量作用域
在程序中定义一个变量时,这个变量是有作用范围的,变量的作用范围被称为它的作用域。根据定义变量的位置,变量分为两种:
- 局部变量。在函数中定义的变量,包括参数,都被称为局部变量
- 全局变量。在函数外面、全局范围内定义的变量,被称为全局变量
每个函数在执行时,系统都会为该函数分配一块“临时内存空间”,所有的局部变量都被保存在这块临时内存空间内。当函数执行完成后,这块内存空间就被释放了,这些局部变量也就失效了。因此离开函数之后就不能再访问局部变量了。
全局变量意味着它们可以在所有函数内被访问。
不管是在函数的局部范围内还是全局范围内,都可能存在多个变量,每个变量“持有”该变量的值。从这个角度看,不管是局部变量还是全局变量,这些变量和它们的值就像一个“看不见”的字典,其中变量名就是字典的Key,变量值就是字典的value。
python提供了三个函数来获取指定范围内的“变量字典”
- globals():该函数返回全局范围内所有变量组成的“变量字典”
- locals():该函数返回当前局部范围内所有变量组成的“变量字典”
- vars(object):获取在指定对象范围内所有变量组成的“变量字典”。如果不传入object参数,vars()和locals()的作用完全相同。
globals()和locals()这两个函数的区别和联系:
- locals()总是获取当前局部范围内所有变量组成的“变量字典”,因此,如果在全局范围内(在函数之外)调用locals()函数,同样会获取全局范围内所有变量组成的“变量字典”;而globals()无论在哪里执行,总是获取全局范围内所有变量组成的“变量字典”
- 一般来说,使用globals()和locals()获取的“变量字典”只应该被访问,不应该被修改。但实际上,不管是使用globals()还是使用locals()获取的全局范围内的“变量字典”,都可以被修改。而这种修改会真正改变全局变量本身;但通过locals()获取的局部范围内的“变量字典”,即使对它修改也不会影响局部变量。
def test ():
age = 20
# 直接访问age局部变量
print(age) # 输出20
# 访问函数局部范围的“变量数组”
print(locals()) # {'age': 20}
# 通过函数局部范围的“变量数组”访问age变量
print(locals()['age']) # 20
# 通过locals函数局部范围的“变量数组”改变age变量的值
locals()['age'] = 12
# 再次访问age变量的值
print('xxx', age) # 依然输出20
# 通过globals函数修改x全局变量
globals()['x'] = 19
x = 5
y = 20
print(globals()) # {..., 'x': 5, 'y': 20}
# 在全局访问内使用locals函数,访问的是全局变量的“变量数组”
print(locals()) # {..., 'x': 5, 'y': 20}
# 直接访问x全局变量
print(x) # 5
# 通过全局变量的“变量数组”访问x全局变量
print(globals()['x']) # 5
# 通过全局变量的“变量数组”对x全局变量赋值
globals()['x'] = 39
print(x) # 输出39
# 在全局范围内使用locals函数对x全局变量赋值
locals()['x'] = 99
print(x) # 输出99
提示:
在使用globals()或locals()访问全局变量的“变量字典”时,将会看到程序输出的“变量字典”默认包含了很多变量,这些都是Python主程序内置的。
全局变量默认可以在所有函数内被访问,但如果在函数中定义了与全局变量同名的变量,此时就会发生局部变量遮蔽(hide)全局变量的情形。示例:
name = 'Charlie'
def test ():
# 直接访问name全局变量
print(name) # Charlie
test()
print(name)
输出结果:
Charlie
Charlie
函数内定义了与全局变量同名的变量,发生局部变量遮蔽(hide)全局变量的情形,示例:
name = 'Charlie'
def test ():
# 直接访问name全局变量
print(name) # Charlie
name= "孙悟空"
test()
print(name)
输出结果:
Traceback (most recent call last):
File "C:Userszz.spyder-py3 emp.py", line 6, in <module>
test()
File "C:Userszz.spyder-py3 emp.py", line 4, in test
print(name) # Charlie
UnboundLocalError: local variable 'name' referenced before assignment
为了避免这个问题,可以通过两种方式来修改上面程序
- 访问被遮蔽的全局变量
可以通过globals()函数来实现,修改如下:
name = 'Charlie'
def test ():
# 直接访问name全局变量
print(globals()['name']) # Charlie
name = '孙悟空'
test()
print(name) # Charlie
输出结果:
Charlie
Charlie
- 在函数中声明全局变量
为了避免在函数中对全局变量赋值(并不是重新定义局部变量),可使用global语句来声明全局变量。可将程序修改如下:
name = 'Charlie'
def test ():
# 声明name是全局变量,后面的赋值语句不会重新定义局部变量
global name
# 直接访问name全局变量
print(name) # Charlie
name = '孙悟空'
print(name)
test()
print(name) # 孙悟空
输出结果:
Charlie
Charlie
孙悟空
增加了“global name”声明语句之后,程序会把name变量当成全局变量,这意味着test()函数后面对name赋值的语句只是对全局变量赋值,而不是重新定义局部变量。
局部函数
前面所看到的函数都是在全局范围内定义的,它们都是全局函数。python还支持在函数体内定义函数,这种被放在函数体内定义的函数称为局部函数。
在默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭(enclosing)函数内有效,其封闭函数也可以返回函数,以便程序在其他作用域中使用局部函数。
# 定义函数,该函数会包含局部函数
def get_math_func(type, nn) :
# 定义一个计算平方的局部函数
def square(n) : # ①
return n * n
# 定义一个计算立方的局部函数
def cube(n) : # ②
return n * n * n
# 定义一个计算阶乘的局部函数
def factorial(n) : # ③
result = 1
for index in range(2, n + 1) :
result *= index
return result
# 调用局部函数
if type == "square" :
return square(nn)
elif type == "cube":
return cube(nn)
else:
return factorial(nn)
print(get_math_func("square", 3)) # 输出9
print(get_math_func("cube", 3)) # 输出27
print(get_math_func("", 3)) # 输出6
输出结果:
9
27
6
如果封闭函数没有返回局部函数,那么局部函数只能在封闭函数内调用,如上面程序。
如果封闭函数将局部函数返回,且程序使用变量保存了封闭函数的返回值,那么这些局部函数的作用域就会被扩大。因此程序完全可以自由地调用他们,就像它们都是全局函数一样。
局部函数内的变量也会遮蔽它所在函数内的局部变量,示例:
def foo ():
# 局部变量name
name = 'Charlie'
def bar ():
# 访问bar函数所在的foo函数的name局部变量
print(name) # Charlie
name = '孙悟空'
bar()
foo()
输出结果:
Traceback (most recent call last):
File "C:Userszz.spyder-py3 emp.py", line 9, in <module>
foo()
File "C:Userszz.spyder-py3 emp.py", line 8, in foo
bar()
File "C:Userszz.spyder-py3 emp.py", line 6, in bar
print(name) # Charlie
UnboundLocalError: local variable 'name' referenced before assignment
python提供了nonlocal关键字,通过nonlocal语句即可声明局部函数的局部变量只是访问该函数所在函数内的布局变量(并没有定义新的布局变量)。示例:
def foo ():
# 局部变量name
name = 'Charlie'
def bar ():
nonlocal name
# 访问bar函数所在的foo函数的name局部变量
print(name) # Charlie
name = '孙悟空'
bar()
print(name)
foo()
输出结果:
Charlie
孙悟空
提示
nonlocal与前面介绍的global功能大致相似,区别只是global用于声明访问全局变量,而nonlocal用于声明访问当前函数所在函数内的布局变量。
函数的高级内容
python的函数本身也是一个对象,函数即可用于赋值,也可用作其他函数的参数,还可作为其他函数的返回值。
使用函数变量
python的函数也是一种值:所有函数都是function对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。
当把函数赋值给变量之后,可通过该变量来调用函数,示例:
# 定义一个计算乘方的函数
def pow(base, exponent) :
result = 1
for i in range(1, exponent + 1) :
result *= base
return result
# 将pow函数赋值给my_fun,则my_fun可当成pow使用
my_fun = pow
print(my_fun(3 , 4)) # 输出81
# 定义一个计算面积的函数
def area(width, height) :
return width * height
# 将area函数赋值给my_fun,则my_fun可当成area使用
my_fun = area
print(my_fun(3, 4)) # 输出12
输出结果:
81
12
通过对my_fun变量赋值为不同的函数,可以让my_fun变量在不同的时间指向不同的函数,从而让程序更加灵活。由此可见,使用函数变量的好处是让程序更加灵活。
使用函数作为函数形参
有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定 --- 这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样既可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。
# 定义函数类型的形参,其中fn是一个函数
def map(data, fn) :
result = []
# 遍历data列表中每个元素,并用fn函数对每个元素进行计算
# 然后将计算结果作为新数组的元素
for e in data :
result.append(fn(e))
return result
# 定义一个计算平方的函数
def square(n) :
return n * n
# 定义一个计算立方的函数
def cube(n) :
return n * n * n
# 定义一个计算阶乘的函数
def factorial(n) :
result = 1
for index in range(2, n + 1) :
result *= index
return result
data = [3 , 4 , 9 , 5, 8]
print("原数据: ", data)
# 下面程序代码3次调用map()函数,每次调用时传入不同的函数
print("计算数组元素的平方")
print(map(data , square))
print("计算数组元素的立方")
print(map(data , cube))
print("计算数组元素的阶乘")
print(map(data , factorial))
# 获取map的类型
print(type(map))
输出结果:
原数据: [3, 4, 9, 5, 8]
计算数组元素的平方
[9, 16, 81, 25, 64]
计算数组元素的立方
[27, 64, 729, 125, 512]
计算数组元素的阶乘
[6, 24, 362880, 120, 40320]
<class 'function'>
从上面介绍不难看出,通过使用函数作为参数可以在调用函数时动态传入函数 --- 实际上就可以动态改变被调用函数的部分代码。
使用函数作为返回值
python还支持使用函数作为其他函数的返回值。示例:
def get_math_func(type) :
# 定义一个计算平方的局部函数
def square(n) : # ①
return n * n
# 定义一个计算立方的局部函数
def cube(n) : # ②
return n * n * n
# 定义一个计算阶乘的局部函数
def factorial(n) : # ③
result = 1
for index in range(2 , n + 1):
result *= index
return result
# 返回局部函数
if type == "square" :
return square
if type == "cube" :
return cube
else:
return factorial
# 调用get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube") # 得到cube函数
print(math_func(5)) # 输出125
math_func = get_math_func("square") # 得到square函数
print(math_func(5)) # 输出25
math_func = get_math_func("other") # 得到factorial函数
print(math_func(5)) # 输出120
输出结果:
125
25
120
局部函数与lambda表达式
lambda表达式时现代编程语言争相引入的一种语法,如果说函数是命名的、方便复用代码块,那么lambda表达式则是功能更灵活的代码块,它可以在程序中被传递和调用。
回顾局部函数
回顾前面介绍的 get_math_func 函数将返回三个局部函数之一,如下代码:
def get_math_func(type) :
# 定义一个计算平方的局部函数
def square(n) : # ①
return n * n
# 定义一个计算立方的局部函数
def cube(n) : # ②
return n * n * n
# 定义一个计算阶乘的局部函数
def factorial(n) : # ③
result = 1
for index in range(2 , n + 1):
result *= index
return result
# 返回局部函数
if type == "square" :
return square
if type == "cube" :
return cube
else:
return factorial
由于局部函数的作用域默认仅停留在其封闭函数之内,因此这三个局部函数的函数名的作用太有限了 --- 仅仅是在if语句中作为返回值使用。一旦离开了get_math_func函数体,这三个局部函数的函数名就失去了意义。
既然局部函数的函数名没有太大意义,那么就考虑使用lambda表达式来简化局部函数的写法。
使用lambda表达式代替局部函数
使用lambda表达式来简化get_math_func函数,将程序改写如下:
def get_math_func(type) :
result=1
# 该函数返回的是Lambda表达式
if type == 'square':
return lambda n: n * n # ①
elif type == 'cube':
return lambda n: n * n * n # ②
else:
return lambda n: (1 + n) * n / 2 # ③
# 调用get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube")
print(math_func(5)) # 输出125
math_func = get_math_func("square")
print(math_func(5)) # 输出25
math_func = get_math_func("other")
print(math_func(5)) # 输出15.0
输出结果:
125
25
15.0
python要求lambda表达式只能是单行表达式,不允许使用更复杂的函数形式。
lambda表达式的语法格式如下:
lambda [parameter_list]: 表达式
lambda表达式的几个要点:
- lambda表达式必须使用lambda关键字定义
- 在lambda关键字之后、冒号左边的是参数列表,可以没有参数,也可以有多个参数。如果有多个参数,则需要用逗号隔开,冒号右边是该lambda表达式的返回值。
实际上,lambda表达式的本质是匿名的、单行函数体的函数。因此,lambda表达式可以写成函数的形式。例如,对于如下lambda表达式:
lambda x,y:x+y
可改写如下函数形式:
def add(x,y):return x+y
上面定义函数时使用了简化语法:当函数体只有一行代码时,可以直接把函数体的代码放在与函数头同一行。
总体来说,函数比lambda表达式的适应性更强,lambda表达式只能创建简单的对象(它只适合函数体为单行的情形。但lambda表达式依然有如下用途:
- 对于单行函数,使用lambda表达式可以省去函数定义的过程,让代码更加简洁。
- 对于不需要多次复用的函数,使用lambda表达式可以在用完之后立即释放,提供了性能。
# 传入计算平方的lambda表达式作为参数
x = map(lambda x: x*x , range(8))
print([e for e in x]) # [0, 1, 4, 9, 16, 25, 36, 49]
# 传入计算平方的lambda表达式作为参数
y = map(lambda x: x*x if x % 2 == 0 else 0, range(8))
print([e for e in y]) # [0, 0, 4, 0, 16, 0, 36, 0]
输出结果:
[0, 1, 4, 9, 16, 25, 36, 49]
[0, 0, 4, 0, 16, 0, 36, 0]
python内置的map()函数的第一个参数需要传入函数,此处传入了函数的简化形式:lambda表达式,这样程序更加简洁,而且性能更好。
本章小结
python语言既支持面向过程编程,也支持面向对象编程。而函数和lambda表达式就是Python面向过程编程的语法基础。必须引起重视。
原文来源于我的语雀,我的微信公众号:细细研磨