Python 匿名函数,装饰器和偏函数
详细内容请参考廖雪峰官网,此处只是一些摘抄,心得与练习的coding。
- 匿名函数
其实就是lambda.
以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) [1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数
lambda x: x * x
实际上就是:def f(x): return x * x
关键字
lambda
表示匿名函数,冒号前面的x
表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写
return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y): return lambda: x * x + y * y
练习:
请用匿名函数改造下面的代码:# -*- coding: utf-8 -*- # def is_odd(n): # return n % 2 == 1 # L = list(filter(is_odd, range(1, 20))) L = list(filter(lambda n:n%2 == 1,range(1,20))) print(L)
- 装饰器
我觉得这是Python一个很牛的发明。就是在不改变现在代码的情况下,在函数名称上一行加上@装饰器,就可以达到改变函数的效果。有点像把函数的指针重新指向了另一个地方。
比如现在有一个now()函数:
>>> def now(): ... print('2018-8-19') ... >>> f = now >>> f() 2018-9-19
假设我们要增强
now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
观察上面的
log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:@log def now(): print('2018-8-19')
调用now()函数:
>>> now() call now(): 2018-8-19
把
@log
放到now()
函数的定义处,相当于执行了语句:now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
这个3层嵌套的decorator用法如下:
@log('execute') def now(): print('2018-8-19')
执行结果如下:
>>> now() execute now(): 2018-8-19
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
因为返回的那个
wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写
wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
练习:
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:# -*- coding: utf-8 -*- import time, functools def metric(fn): @functools.wraps(fn) def wrapper(*args,**kw): gap_begin = time.time() r = fn(*args,**kw) gap_end = time.time() print('%s executed in %.2f ms' % (fn.__name__, gap_end-gap_begin)) return r return wrapper # 测试 @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; f = fast(11, 22) s = slow(11, 22, 33) if f != 33: print('测试失败!') elif s != 7986: print('测试失败!') print(fast.__name__) print(slow.__name__)
-
偏函数
这里的偏函数和数学意义上的偏函数不一样。在介绍函数参数的时候,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点.
例如:int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N进制的转换:>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565
假设要转换大量的二进制字符串,每次都传入
int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:def int2(x, base=2): return int(x, base)
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85
所以,简单总结
functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
最后,创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数,当传入:max2 = functools.partial(max, 10)
实际上会把
10
作为*args
的一部分自动加到左边,也就是:max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7) max(*args)
结果为10