带参数的装饰器
装饰器是一个函数(可以称为装饰器函数),他的参数就是被装饰的函数(可以被称为基础函数),函数体是定义一个内部函数(可以被称为包装器函数),在包装器函数中调用基础函数并添加“装饰”功能,并返回基础函数的执行结果。最后装饰器函数以包装器函数为返回值。 调用装饰器返回包装器函数(这时基础函数并未被调用)。将返回的包装器函数赋值给基础函数同名变量,再执行与基础函数名同名的变量(这个变量指向包装器函数,可以被调用)从而执行装饰功能和基础函数的功能。
python中这个装饰器实现语法是不能改动的,为了引入额外自定义的参数,需要把 “装饰器”封装起来:即在外层函数中定义“装饰器函数”,最后外层函数返回装饰器函数, 而装饰器函数仍然依上述规定,而额外的参数可以通过外部函数引入,示例如下:
def out_decorator(arg1, arg2):
print('step1')
def decorator(decorated_func):
print('step3')
def wrapper(*args,**kw):
print('step5')
outcome = decorated_func(*args,**kw)
print('step6')
return outcome
print('step4')
return wrapper
print('step2')
return decorator
可以如下方式使用这个带参数的装饰器
@out_decorator(1,'car')
def decorated_func(*args,**kw):
print(args)
很多文章说经过装饰的decorated_func('hello') 的调用相当于:out_decorator(1, 'car')(decorated_func)('hello'),但实际的执行还是有差异的。比较上述两种函数的执行过程,
decorated_func('hello')
print('----------')
out_decorator(1, 'car')(decorated_func)('hello')
从step1 到step6 如上述代码中的打印语句所示。
step1
step2
step3
step4
step5
('hello',)
step6
----------
step1
step2
step3
step4
step5
step5
('hello',)
step6
step6
step5 和step6分别被执行了两次。
装饰器与__name__
调用完成之后,新的问题又出现了:函数的__name__属性因为使用装饰器改变了。比如 提交打印语句print(decorated_func.__name__) 。我们期待结果是decorated_func 但结果显示wrapper。这是因为经过修饰器的修饰后我们实际执行的是修饰器函数。
解决方法一:
python为了解决这个问题提供了一装饰器:@functools.wraps(fun),这个装饰器的作用就是将被修饰的函数的__name__属性改为指定的名称,用这个装饰器修饰我们的自定义装饰器函数(是不是很有趣?)。如下所示:(记得先导入functools包)
import functools
def out_decorator(arg1, arg2):
print('step1')
def decorator(decorated_func):
print('step3')
@functools.wraps(decorated_func)
def wrapper(*args,**kw):
print('step5')
outcome = decorated_func(*args,**kw)
print('step6')
return outcome
print('step4')
return wrapper
print('step2')
return decorator
解决方法二:
既然我们最后实际执行的是包装器函数,我们把在返回包装器函数前把包装器函数的__name__的值更新为基础函数的__name__值即可。
def out_decorator(arg1, arg2):
print('step1')
def decorator(decorated_func):
print('step3')
def wrapper(*args,**kw):
print('step5')
outcome = decorated_func(*args,**kw)
print('step6')
return outcome
print('step4')
wrapper.__name__=decorated_func.__name__
return wrapper
print('step2')
return decorator
@out_decorator(1,'car')
def decorated_func(*args,**kw):
print(args)
print(decorated_func.__name__)
类装饰器
我们知道装饰器是一个函数,python中函数是一个可被调用的对象,类和类对象也可以被调用:当将类名当作函数形式调用时其实是调用了类的__init__方法返回一个类对象变量;当将类对象变量名当作函数形式调用时调用了类对象的__call__方法。这与装饰器的概念很像,因此我们可以通过 __init__和__call__函数实现装饰器效果。如下所示:
不带参数的类装饰器
被基础函数当作__init__参数传入,在__call__函数中添加装饰功能并调用基础函数。类装饰器同样会有__name__改变的问题,不带参数的类装饰器装饰后的函数本质是指向了类装饰器的一个对象,因此__name__返回的是类对象的__name__属性,因此为不带参数的类装饰器的类添加设置实例属性__name__为基础函数的的__name__值即可。
class decorator_cls():
def __init__(self, decorated_f):
self.decorated_f= decorated_f
self.__name__=decorated_f.__name__
def __call__(self,*arg,**kw):
print("step1")
self.decorated_f(*arg,**kw)
print("step2")
@decorator_cls
def hello(*arg, **kw):
print(arg)
hello('world')
带参数的类装饰器
装饰器的参数是从__init__函数传入,基础函数则当作__call__函数的参数传入,并在__call__函数中定义并返回包装器函数。带参数的类装饰器装时后的基础函数实际是指向了__call__函数的返回的包装器函数,因此对此包装器函数用@functools.wraps(decorated_f)装饰器进行装饰即可重新恢复__name__的值。也可以用上述的方法二,为包装器函数的__name__重新赋值。
import functools
class decorator_cls():
def __init__(self, arg1,arg2):
self.arg1= arg1
self.arg2=arg2
def __call__(self,decorated_f):
@functools.wraps(decorated_f) #方法一
def wrapper(*arg,**kw):
print("step1")
decorated_f(*arg,**kw)
print("step2")
wrapper.__name__=decorated_func.__name__ #方法二
return wrapper
@decorator_cls('d','c')
def hello(*arg, **kw):
print(arg)
@decorator_cls('d','c')
def hello(*arg, **kw):
print(arg)
hello('world')