Python高阶函数之装饰器和偏函数
Posted 礁之
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python高阶函数之装饰器和偏函数相关的知识,希望对你有一定的参考价值。
文章目录
此文章参考:装饰器 - 廖雪峰的官方网站 (liaoxuefeng.com)
一、装饰器
- 由于函数也是一个对象,并且函数可以赋值给变量,赋值后的变量可以直接调用函数:
>>> def test():
... print("aaaaaa1")
...
>>> f = test
>>> f
<function test at 0x0000022862EBE7A0>
>>> f()
使用__name__可以获取函数的函数名:
>>> test.__name__
'test'
>>> f.__name__
'test'
- 如果要增加
test( )
函数的功能,例如在函数调用前后增加输出内容,想要达到这种效果,我们可以直接修改函数,但是如果说不允许修改源函数,又需要增加函数功能该怎么做呢,这种情况我们可以使用装饰器(Decorator)
- 装饰器其实就是在不修改源函数的情况下,在代码运行期间动态增加函数功能的方式,而装饰器本质上就是一个返回函数的高阶函数
- 下面就来看一下装饰器的使用案例:
# -*- coding: utf-8 -*-
def log(func): #装饰器,接收一个函数,返回一个函数
def wrapper(*args,**kw):
func() #这里调用了函数,因为没有定义参数,所以最后输出args= (),kw=
print("name is %s()" % func.__name__)
return func(*args,**kw) #这里调用了传入的函数,也就是now函数,并且定义了参数,最后输出args= (1, 2, 3),kw= 'a': 'aaa'
print("aaa")
return wrapper
@log #@符号把装饰器log置于now函数定义处,相当于执行了"now = log(now)"
def now(*args,**kw):
print("args= %s,kw= %s" % (args,kw))
if __name__=='__main__':
now(1,2,3,a="aaa") #调用函数并且传入参数
print(now.__name__)
#输出:
aaa
args= (),kw=
name is now()
args= (1, 2, 3),kw= 'a': 'aaa'
wrapper
#解析
由于log()是一个装饰器,会返回一个函数,但是原来的now函数依然存在,上面只是把同名的now变量指向了新的函数,然后调用now执行新的函数,也就是在log函数中返回wrapper函数
wrapper函数的参数定义是(*args,**kw),这样定义参数可以使wrapper函数接受任意参数的调用
- 如果装饰器本身需要传入参数,那就需要编写一个返回装饰器的高阶函数:
# -*- coding: utf-8 -*-
def log(test):
def deco(func):
def wrapper(*args,**kw):
print("%s %s()" % (test,func.__name__))
return func(*args,**kw)
return wrapper
return deco
@log('name is ')
def now():
print("aaaaa")
if __name__=='__main__':
now()
print(now.__name__)
#输出:
name is now()
aaaaa
wrapper
#解析:
其实看过上面的案例后,这里传入装饰器参数就很好理解了,“name is”赋值给了test参数,最终返回的还是wrapper函数,和上面的两层嵌套相比,这里的三层嵌套其实就是now = log('name is ')(now),log('name is ')返回的是装饰器函数deco,然后装饰器函数deco再调用now即deco(now),最终返回wrapper函数,先输出“name is now()”,然后调用now函数输出”aaaaa“
- 看过上面的案例之后,可以看到最后一步输出
print(now.__name__)
时,输出了wrapper
,now
的函数名称从now
变成了wrapper
,这是因为装饰器返回的wrapper
函数的函数名就是wrapper
,所以,需要把原始函数now
的__name__
等属性复制到wrapper
函数中,不然有些依赖函数签名的代码执行时就会报错 - 我们可以使用
wrapper.__name__ = func.__name__
这种代码来解决上面的问题,但是不需要,因为Python内置的functools.wraps
就是做这个事情的,例如:
1、正常装饰器:
# -*- coding: utf-8 -*-
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print("name is %s()" % func.__name__)
return func(*args,**kw)
return wrapper
@log
def now(*args,**kw):
print("%s %s" % (args,kw))
if __name__=='__main__':
now(1,2,3,a='aaa')
print(now.__name__)
#输出:
name is now()
(1, 2, 3) 'a': 'aaa'
now
2、带参数的装饰器:
# -*- coding: utf-8 -*-
import functools
def log(test):
def deco(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print("%s is %s()" % (test,func.__name__))
return func(*args,**kw)
return wrapper
return deco
@log('name')
def now(*args,**kw):
print("%s %s" % (args,kw))
if __name__ == '__main__':
now(1,2,3,a="ccc")
print(now.__name__)
#输出:
name is now()
(1, 2, 3) 'a': 'ccc'
now
可以看到,不管是带参数还是不带参数的装饰器,在加了@functools.wraps(func)后,最后输出now.__name__时,函数名称还是now
- 这里引用一个小练习:
# -*- coding: utf-8 -*-
import time, functools
def metric(fn):
@functools.wraps(fn)
def wrapper(*args,**kw):
start_time = time.time()
res = fn(*args,**kw)
end_time = time.time()
print('%s executed in %s ms' % (fn.__name__, end_time-start_time))
return res
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 and s == 7986:
print('测试成功')
#输出:
fast executed in 0.013970375061035156 ms
slow executed in 0.12478971481323242 ms
测试成功
二、偏函数——functools.partial
- 在上面的装饰器中,使用了模块
functools
,除了functools.wraps
,还有很多功能,其中有一个就是偏函数(Partial function)
,注意,这里的偏函数和数学意义上的偏函数不一样 - 在定义函数时,通过设置参数的默认值,也就是设置默认参数,可以降低调用函数的难度,而偏函数也可以做到这一点,例如:
1、int()函数可以把字符串转换为整数,当只传入字符串时,int函数默认按照十进制转换,也可以加base参数,执行进制的转换,要注意,int转换需要是字符串
>>> int('12345')
12345
>>> int('12345',base=8)
5349
>>> int('12345',base=16)
74565
>>> int('111000',2)
56
2、如果需要转换大量的字符串为指定进制,那么我们可以定义一个函数
>>> def test(x,base=2):
... return int(x,base)
...
>>> test("11100")
28
>>> test("111")
7
3、而functools.partial就是帮助我们创建一个偏函数的,像上面那种情况,不需要我们自己定义函数,可以直接使用functools.partial创建一个新函数
>>> import functools
>>> test = functools.partial(int,base=2)
>>> test("1111")
15
>>> test("1111000")
120
>>> test("1111000",base=10) #可以看到,在调用test函数添加参数base后也是可以正常调用的,说明functools.partial只是定义了默认参数
1111000
4、在创建偏函数时,可以接收不同的参数,例如可变参数*args和关键字参数**kw
>>> test = functools.partial(int,base=2)
>>> kw = 'base':10
>>> test("11111",**kw)
11111
>>> test_1 = functools.partial(max,10)
>>> args = (12,55,4)
>>> test_1(*args)
55
- 当函数的参数个数太多,需要简化时,使用
functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。 - 在内置函数或自定义函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。如果是自定义函数,我们可以定义默认参数,而内置函数在这种情况下,我们就可以使用偏函数,定义一个新的函数,并且指定默认参数
这里偏函数的作用其实就是在原函数的基础上创建一个新的函数,并且指定默认参数
太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
- 在内置函数或自定义函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。如果是自定义函数,我们可以定义默认参数,而内置函数在这种情况下,我们就可以使用偏函数,定义一个新的函数,并且指定默认参数
这里偏函数的作用其实就是在原函数的基础上创建一个新的函数,并且指定默认参数
以上是关于Python高阶函数之装饰器和偏函数的主要内容,如果未能解决你的问题,请参考以下文章