装饰器
先理解一个柯里化:
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
即 add(a,b), add(a)(b), 这两个运行效果一样
@语法糖在什么时候干了啥事? @语法糖只做一件事那就是 仅在被加载或导入时把被装饰函数传入装饰函数中并返回新的函数,且只运行一次,这点很重要一定要理解 @的后面是一个可调用的,且参数是接收一个函数的对象,即@x, x = f1(fn)比如函数的引用,或实现__call__函数的类
装饰器分解说明:
不管带不带参啥的。万变不离其中。
@a
def b():
pass
这种的分解为: b(): b = a(b) >> b(), 此时调用的b()其实就是调用b = a(b) 然后 b(), a为装饰器函数的引用接受一个函数做参数,返回一个新函数,然后调用,如果被装饰的函数由参数直接带上就是了, 只不过a(b)返回的函数也必须是一个带参数的。如下
@a
def b(x, y): 等价于b = a(b) >> b(x, y), 即a(b) 返回了一个接受两个参数的函数
pass
这种的装饰器应该是这样的:
def a(fn):
def wrapper(*args, **kwargs):接受被装饰函数的参数
do some thing
return wrapper
return wrapper 返回新函数,在导入是就已经执行了,所以装饰器在导入后装饰器参数和装饰的函数就不能改变的了
@a(c, d)
def b():
pass
这种的分解为: b(): b = a(c, d)(b) >> b(), 此时调用的b()其实就是调用b = a(c, d)(b) 然后 b(), a(c, d)类似1中的a,所以这种会多一层,先返回上面1中的a,在返回新函数,再调用执行,
这种装饰器应该是这样的:
def outa(c,d): 先接受两个参数, 返回一个1上面的a, 在由@a同样的原理来执行,只是多了一步闭包而已
def a(fn):
def wrapper(*args, **kwargs):接受被装饰函数的参数
do some thing
return wrapper
return wrapper 返回新函数,在导入是就已经执行了,所以装饰器在导入后装饰器参数和装饰的函数就不能改变的了
return a
以下看例子
def decor(fn):
return 2
@decor #等价于 add = decor(add),在被加载是就已经运行装饰器替换了原函数,且只运行一次
def add():
return 1
print(add)
def decor(fn):
def wrapper():
"""wo shi decorator"""
return 2
return wrapper
@decor #等价于 add = decor(add),在被加载是就已经运行装饰器替换了原函数,且只运行一次
def add():
return 1
#此时name为wrapper,即此时的调用已经是在调用docor里的wrapper了,说明在加载时函数add已经被装饰器替换成了新函数
print(add.__name__)
print(add.__doc__) #此时的doc都被替换成了 decor的了
print(add())
# 被装饰函数带参数
def decor(fn):
# 根据需求而定参数
def wrapper(*args, **kwargs):
fn()
return args
return wrapper
@decor #等价于 add = decor(add)
def add(x, y, z):
return 1
#此时name为wrapper,即此时的调用已经是在调用docor里的wrapper了,说明在加载时函数add已经被装饰器替换成了新函数
print(add.__name__)
rint(add.__doc__) #此时的doc都被替换成了 decor的了
print(add(1,2,3))
# 其实这边add(1,2,3)其实调用的是 decor下面的wrapper(1,2,3), 因为此时的add 就是函数 wrapper
# 其实调用顺序是: decor(add)(1,2,3)
# 装饰函数带参数
# 由上面的可以理解@decor 的作用等价于 add = docor(add)
# @后面的 docor 是函数的引用, @语法糖会自己把被装饰的函数作为参数调用@后面的函数即 add = doctor(add)
# 那么@后面只要是函数的引用就行了对吧,那是不是@后面可以跟一个函数调用,此调用返回一个函数的引用,即上面的decor函数
# outdecor函数调用后返回decor的函数引用
def outdecor(a, b):
def decor(fn):
# 根据需求而定参数
def wrapper(*args, **kwargs):
fn(*args)
return args
return wrapper
return decor
@outdecor(1,2) #等价于 add = outdecor(1,2)(add)
def add(x, y, z):
return 1
print(add.__name__)
print(add(1,2,3))
#上面的@outdecor(1,2),真实操作就是,先调用了outdecor(1,2) 返回了一个内层的函数decor的引用,这样就把装饰器的参数传进去了(由闭包实现)
# 由开发需要我们需要把add的__doc__改回自己原本的,或者还原其他属性
# 我们也可以写一个装饰器,还原属性
def restore_property(fn): #这边的fn就是接受的add函数的
def wrapper(wrappered):# 这边的wrappered是接受 outdecor中的wrapper的,
#这里面没有对wrappered函数进行调用,只是改动了wrappered对属性, 所以没有接受参数对那层函数
wrappered.__name__ = fn.__name__
wrappered.__doc__ = fn.__doc__
#这个return一定要, 因为在下面的@restore_property(fn)需要有一个函数 func(wrapper)
#并返回outdecor中的wrapper, 即上面讲的@后面必需是个函数对引用,或一个函数对调用且调用后返回一个函数对引用
return wrappered
return wrapper
def outdecor(a, b):
def decor(fn):
@restore_property(fn) #装饰器里的装饰器
def wrapper(*args, **kwargs):
"""wo shi decorator"""
enter = fn(*args)
return enter #这个return 可有可无, 这边主要是fn()调用的返回值, 有需要的话就要
return wrapper #这边的wrapper 其实就是上面return 回来的wrappered
return decor
@outdecor(1,2) #等价于 add = outdecor(1,2)(add)
def add(x, y, z):
"""wo shi add """
return x + y + z
print(add.__name__)
print(add.__doc__)
print(add(1,2,3))
# 用类写一个带参数的装饰器, 且还原doc等属性
class RestoreProperty(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, wrappered):
wrappered.__name__ = self.fn.__name__
wrappered.__doc__ = self.fn.__doc__
return wrappered
class Decor(object):
def __init__(self, c, d):
self.c = c
self.d = d
def __call__(self, fn):
@RestoreProperty(fn)
def wrapper(x, y, z):
"""wo shi 类装饰器"""
print('before')
enter = fn(x, y, z)
print('after')
return enter
return wrapper
@Decor(1,2) #等价于 add = outdecor(1,2)(add)
def add(x, y, z):
"""wo shi add """
return x + y + z
print(add.__name__)
print(add.__doc__)
print(add(1,2,3))
来源:oschina
链接:https://my.oschina.net/u/3956570/blog/4315246