前言
在项目开发或者面试中,装饰器都是很重要的一个话题。装饰器的强大之处在于它能够在不修改原有业务逻辑的情况下对代码进行拓展,它能够最大程度的对代码进行复用。现在一步一步来理解装饰器。
函数
在python中一切皆对象,那么函数也不例外,它也可以作为其他函数的返回值,函数的参数。
作为函数的返回值
def foo(num):
return num+1
def bar():
return foo
print(bar()(2))#3
#等价于
#print(foo(2))
在调用函数bar()的返回值是一个函数对象,因为返回的是函数,所以我们可以继续对返回值进行调用(调用就是在函数名后面加())
函数作为参数
函数也可以作为另一个函数的参数
def foo(num):
return num+1
def bar(func):
return func(2)
print(bar(foo))#3
函数 bar 接收一个参数,这个参数是一个可被调用的函数对象,把函数 foo 传递到 bar 中去时,foo 和 fun 两个变量名指向的都是同一个函数对象,所以调用 fun(3) 相当于调用 foo(3)。
函数嵌套
函数不仅能作为参数和返回值,函数还可以定义在另一个函数中,作为嵌套函数的存在
def outer():
x=1
def inner():
print(x)
inner()
outer()#1
inner做为嵌套函数,它可以访问外部函数的变量,调用 outer 函数时,发生了3件事:
1、给 变量 x 赋值为1
2、定义嵌套函数 inner,此时并不会执行 inner 中的代码,因为该函数还没被调用,直到第3步
3、调用 inner 函数,执行 inner 中的代码逻辑。
这里只要在改一下,就成了简单的闭包
闭包
关于闭包,参考这一篇闭包的理解
def outer():
x=1
def inner():
print(x)
return inner
print(outer()())#1
同样是嵌套函数,只是稍改动一下,把局部变量 x 作为参数了传递进来,嵌套函数不再直接在函数里被调用,而是作为返回值返回,这里的 closure就是一个闭包,本质上它还是函数,闭包是引用了自由变量(x)的函数(inner)。
函数装饰器
基于上面的一些理论,现在终于到了装饰器。装饰器的本质是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象
普通装饰器
一步一步写装饰器
def foo():
print("foo")
现在我们想在不改变上面的函数情况下加上日志输出,应该怎么做:
def foo():
print("foo")
def outer(func):
def inner():
print("logging start")
func()
print("logging end")
return inner
foo=outer(foo)
foo()
# logging start
# foo
# logging end
在没有修改foo函数里面的任何逻辑,就重新修改了foo的执行效果,现在来分析下他的执行流程。
这里的 outer 函数其实就是一个装饰器,装饰器是一个带有函数作为参数并返回一个新函数的闭包,本质上装饰器也是函数。outer 函数的返回值是 inner 函数,在 inner 函数中,除了执行日志操作,还有业务代码,该函数重新赋值给 foo 变量后,调用 foo() 就相当于调用 inner()。
使用语法糖:python为装饰器提供了@语法糖,它用在函数的定义上:
@outer
def foo():
print("foo2")
foo()
这样就省去了手动给foo重新赋值的步骤。
带参数的装饰器
如果装饰器本身是需要传入参数的,那么这时就需要一个带参数的装饰器了。
def sayHello(hello):
def wrapper(func):
def inner(*args,**kw):
print("logging start")
print(hello)
func(*args,**kw)
print("logging end")
return inner
return wrapper
@sayHello("hello")
def agam(name):
print(name)
agam("agamgn")
# logging start
# hello
# agamgn
# logging end
被多个装饰器装饰
一个函数是可以被多个装饰器修饰的
def outer1(func):
def inner(*args,**kw):
print("outer1 start")
func(*args,**kw)
print("outer1 end")
return inner
def outer2(func):
def inner(*args,**kw):
print("outer2 start")
func(*args,**kw)
print("outer2 end")
return inner
@outer1
@outer2
def foo():
print("foo")
foo()
#outer1 start
#outer2 start
#foo
#outer2 end
#outer1 end
上面的程序执行的顺序是里到外,所以它等效于:
foo=outer1(outer2(foo))
类似于Koa框架中的洋葱模型。
类装饰器
普通类装饰器
除了函数有装饰器外,类也是可以作为装饰器的,其实只要是一个“可被调用(callable)的对象则可以被当作装饰器。相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的call方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
import time
class Foo:
def __init__(self,func):
self._func=func
def __call__(self):
print("class decorator start")
self._func()
print("class decorator end")
@Foo
def now():
print(time.strftime("%Y-%m-%d",time.localtime(time.time())))
now()
#class decorator start
#2020-02-03
#class decorator end
带参数的类装饰器
同样,类装饰器也是可以带参数的。
class logger:
def __init__(self,level='INFO'):
self.level=level
def __call__(self,func):
def wrapper(*args,**kwargs):
print("[{level}]:the function {func}() is run".format(
level=self.level,func=func.__name__))
func(*args,**kwargs)
return wrapper
@logger(level="WARNING")
def say(something):
print("say {}!".format(something))
say("hello")
# [WARNING]:the function say() is run
# say hello!
带参数和不带参数的类装饰器有很大的不同。
- _ _ init_ _ :不再接收被装饰函数,而是接收传入参数。
- _ _ call_ _ :接收被装饰函数,实现装饰逻辑。
装饰类的装饰器
修饰类的装饰器不常见也不常用,但是和修饰函数的装饰器是一样的。
def typed(**kwargs):
def deco(obj):
for k, v in kwargs.items():
setattr(obj, k, v)
return obj
return deco
@typed(x=1, y=2, z=3)
class Foo:
pass
print(Foo.__dict__)
@typed(name='alex')
class Bar:
pass
print(Bar.__dict__)
#{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3}
#{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None, 'name': 'alex'}
网友评论