顾名思义,匿名函数直观上就是省去命名的函数。Python 中通过lambda
关键字定义匿名函数,格式为
lambda parameter_list : expression
匿名函数接收的参数列表等同于func(param)
中的参数列表param
,区别在于函数体,匿名函数中只能是一些简短的表达式,不能定义、赋值变量,以表达式的计算结果作为返回值
匿名函数同样可以作为赋值对象
def add(x,y):
return x+y
lam_add = lambda x,y: x+y # 同样可以作为赋值对象
add(1,2)
lam_add(1,2)
lambda x,y: a = x+y # 不允许赋值,只能写表达式
从定义中可以看出匿名函数的突出特点在于表达式部分,也正因如此,其他语言如 C#,Java 称这种程式为 lambda 表达式,匿名函数另有其专门的形式
lambda 表达式里常常会有一种叫做三元运算的操作,形式如下
true_res if expression else false_res
当expression
为真时返回true_res
,反之返回false_res
。其效果等同于 Java 等其他语言中的三元运算 expression ? true_res : false_res
别看其中带有if else
关键字,它可不是条件语句,只是特殊点的表达式
1 if 2 < 3 else 0
另一个常和 lambda 表达式一同出现的是映射(map),注意这是一个类,逻辑等同于数学中的映射,将输入空间进行某些运算投射到输出空间中。map 格式如下
map(func, *iterables) --> map object
其接收一个函数和可迭代数据结构组成的参数列表,计算结果保存为一个 map 对象并将其返回。代码形式上看,map 相当于简化版本的 for 循环,对每一个元素都进行相同的 func
操作
def double(x):
return 2*x
res = map(double, [1,2,3,4,5])
map 对象的内容通过可迭代数据结构初始化后查看
list(res)
有了 map 和 lambda 表达式后,一些简单的运算逻辑就可以很简洁的表达出来,计算效率并不会发生变化。
lambda 表达式可以充当 map 中的 func
功能,比如上面求取2*x
的计算逻辑就可以使用下面的方法替代
input_x = [1,2,3,4,5,6]
list(map(lambda x : 2*x, input_x))
因为 map 可以接收变长参数列表,所以可以再复杂些,但要注意 lambda 表达式的参数个数和 map 参数列表长度要相同
input_x = [1,2,3,4,5,6]
input_y = [1,2,3,4,5,6]
list(map(lambda x,y : 2*x + y, input_x, input_y))
要留意参数列表中各参数长度一般要相同,如果内部元素个数不相同,以元素个数最小的参数为准,即保证运算逻辑成立的最大元素个数
input_x = [1,2,3,4,5,6]
input_y = [1,2,3,4]
list(map(lambda x,y : 2*x + y, input_x, input_y)) # 结果只有 4 个值
input_x = [1,2,3,4] # 是谁少了无所谓
input_y = [1,2,3,4,5,6]
list(map(lambda x,y : 2*x + y, input_x, input_y))
lambda 表达式也常和缩减计算(reduce)一起使用,Python 中使用 reduce 要从 functools
模块中导入,reduce 是一个函数,其格式如下:
reduce(function, sequence[, initial]) -> value
reduce会从左到右依次从sequence
中取出两个值进行function
计算,然后将计算结果作为参数之一和下一个sequence
元素一起再一次进行funciton
计算,不断重复直到sequence
“消耗”完毕,这也是“缩减”的由来。如果指定了initial
,第一次计算时initial
会参与计算,也就是说第一次从sequence
中只取一个值
和 lambda 同用时注意因缩减计算每次有两个参数,lambda 表达式的参数也必须是两个
from functools import reduce
input_x = [1,2,3,4]
reduce(lambda x,y: x+y, input_x) # 1+2+3+4
input_y = ['1','2','3','4']
reduce(lambda x,y: x+y, input_y, 'begin:') # 字符串拼接
再来看一个常和 lambda 表达式一起使用的兄弟,过滤器 filter,这也是一个类,形式如下:
filter(function or None, iterable) --> filter object
filter 接收两个参数,一个是过滤判别函数function
,该函数必须有返回值且返回值能判断真假,另一个是要遍历的数据iterable
。如果funciton
为空,保留iterable
里值为真的内容
filter 返回的也是一个对象,所以需要用 list 之类的数据结构获取里面的内容
input_x = [1,0,1,0,1,0,1]
list(filter(None, input_x)) # 1,0 本身就可以代表真与假
input_x = [1,2,3,4,5,6]
list(filter(lambda x: x%2, input_x)) # 筛选奇数
map, reduce, filter 和 lambda 表达式是函数式编程的特征标识,lambda 表达式更是可以视为最基本的算子。相对的,命令式编程的标志性特征是函数定义 def,if-else 语句以及循环。函数式编程更强调结果而不是过程。
函数式编程相较于命令式编程,有以下优点:
函数式编程中的函数实质上不是计算机编程概念里的函数,而是数学里的函数,即一种映射关系。也就是说函数输出值只和参数有关,与其他状态无关,只要参数不变,无论调用多少次结果都一样。
函数式编程里的变量也不是通常意义上的变量,即状态存储单元,实质是数学里的变量,是一个值的名称,变量值不可变,也就是不能像命令式编程中那样多次赋值。x = x + 1
程序中可行是依赖于状态可变,在数学中会被判定为假
无论使用哪种编程语言,总免不了对代码的补充、修订和重构,日积月累,人们为了提高开发效率总结出了一些经验和一系列原则。其中有一个很重要的原则——开闭原则,说的是
对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的
就是说当需求变更,不应该直接修改旧有代码内容,而是通过扩展类或者函数功能实现这一目的。比如说下面有若干函数,分别打印-
,+
,*
构成的分割线
def line_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '-'
print(res)
def plus_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '+'
print(res)
def multiple_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '*'
print(res)
line_split()
plus_split()
multiple_split()
突然我们有了新的需求,要求输出这些函数的运行时长。一个直观的想法是逐个添加功能代码
from time import time
def bad_change(): # 违反开闭原则
start = time()
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '-'
end = time()
print(res)
print(start - end)
这样直接修改旧有函数内容违反了开闭原则。因为只是一两个函数进行简单的修改或许看不出,但当很多函数都要增加这一功能,之后又要撤销这个功能,都要像这样直接修改内部内容不仅繁琐且容易出错。
稍好些的设计是重新设计一个函数,专门负责运行时间的计算
import time
def time_cost(function): # 虽然实现了功能拓展,但也将功能分离了
start = time.time()
function()
end = time.time()
print('time costs: %.3fs'% (end - start))
time_cost(line_split)
time_cost(plus_split)
time_cost(multiple_split)
这样我们就实现了功能的便捷拓展,但却将功能分离了,我们的本意是让原有函数本身具备这样一种功能,而不是将这种功能外包给其他函数。
这时就要用到装饰器了,Python 的装饰器类似于 C# 的特性,Java 的注解。装饰器一般结构定义如下
def decorator(function):
def wrapper():
function()
return wrapper
wrapper
实现了对function
的扩展并被decorator
返回。通过与语法糖的配合实现既拓展功能,又不分离功能,语法糖使用方法如下
@decorator
def function():
pass
from time import time
def time_out(function):
def wrapper():
start = time()
function()
end = time()
print('time costs: %.6f' % (end - start))
return wrapper
@time_out # 就像加了一个挂件一样
def line_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '-'
print(res)
@time_out
def plus_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '+'
print(res)
@time_out
def multiple_split():
res = ''
for i in range(1000000):
if i % 100000 == 0:
res += '*'
print(res)
line_split()
multiple_split()
plus_split()
通过装饰器我们既扩展了功能,同时调用方式也没有发生变化。语法糖保证了易读性。
但还有一些疏漏,这些分隔符函数不用接收参数。当原函数参数列表不为空时,装饰器通过给wrapper
传入可变参数列表*args
进行匹配
from time import time
def time_out(function):
def wrapper(*args): # 接收可变参数
start = time()
function(*args)
end = time()
print('time costs: %.6f' % (end - start))
return wrapper
@time_out # 不用因参数个数不同而重新定义新的装饰器
def char_split(char):
res = ''+char
for i in range(1000000):
if i % 100000 == 0:
res += char
print(res)
@time_out
def chars_split(char_one, char_two):
res = ''
for i in range(1000000):
if i % 200000 == 0:
res += char_one
elif i % 100000 == 0:
res += char_two
print(res)
char_split('a')
chars_split('a', 'b')
再进一步,如果不只是参数数量不定,可能还有关键字参数,于是得到了最常见的装饰器形态
def decorator(function):
def wrapper(*args, **kw):
function(*args, **kw)
return wrapper
from time import time
def time_out(function):
def wrapper(*args, **kw): # 接收可变参数,关键字参数,可以应对任意形式的参数列表
start = time()
function(*args, **kw)
end = time()
print('time costs: %.6f' % (end - start))
return wrapper
@time_out
def note_char_split(char_one, char_two, **kw):
res = ''
for i in range(1000000):
if i % 200000 == 0:
res += char_one
elif i % 100000 == 0:
res += char_two
res += '\n' + str(kw)
print(res)
note_char_split('-','|', chars_type='str', chars_num=2)
此外,每个函数不止可以接受一个装饰器
@decorator_1
@decorator_2
def function():
pass