一、装饰器
- 由于函数也是一个对象,并且函数可以赋值给变量,赋值后的变量可以直接调用函数:
>>> 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可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
在内置函数或自定义函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。如果是自定义函数,我们可以定义默认参数,而内置函数在这种情况下,我们就可以使用偏函数,定义一个新的函数,并且指定默认参数
这里偏函数的作用其实就是在原函数的基础上创建一个新的函数,并且指定默认参数