装饰器
特殊的类型声明,它能够附加到类声明、方法、数据或参数上,可以修改类的行为。通俗来水装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无参数)、装饰器工厂(可传参数)
Python 装饰器
Python 装饰器(decorator)本质上是一个嵌套函数,它接受被装饰函数作为参数,并返回一个包装过的函数。这样我们可以在不改变被装饰器函数代码的情况下给被装饰函数或程序添加新功能。Python 装饰器被广泛应用于缓存、权限校验、性能测试和插入日志等应用场景中,有了装饰器我们可以抽离出大量与函数功能本身无关的代码,增加一个函数的复用性。通俗来讲装饰器作用就是在已存在的对象在不对原来代码进行深度入侵的情况下添加额外的某些功能
例如有如下一个场景,我需要计算某个函数的运行时间我们会怎么做?
import time
# 事例函数
def run():
time.sleep(5)
print('ok')
# 方式一:侵入式统计 直接在函数体中加入统计代码
def run1():
start = time.time()
time.sleep(5)
print('ok')
end = time.time()
# 方式二:使用装饰器
# 定义装饰器
def run_decorator(func):
def inner():
start = time.time()
func()
end = time.time()
print('during: ', end - start)
return inner
# 使用装饰器
@run_decorator
def run2():
time.sleep(5)
print('ok')
从上面两种解决方法的场景中我们可以看出:
方式一对代码侵入大,假如我还想统计一个其他函数运行时间还需要在重写一遍同样的逻辑,没有复用性。
方式二对代码侵入下,且能够方便我们复用到其他地方
因此在实际的业务场景中我们要灵活的使用装饰器,是我们代码更加简洁、复用性更强。既然装饰器这么好用下面我们来具体聊聊装饰器的实现。在了解装饰器的实现我们需要先了解嵌套函数和闭包(Python 装饰器工作原理主要依赖于嵌套函数和闭包)
嵌套函数
如果在一个函数的内部还定义了另一个函数**(注意⚠️: 是定义,不是引用**),这个函数就叫嵌套函数。外部的我们叫它外函数,内部的我们叫他内函数。
我们先来看一个最简单的嵌套函数的例子。我们在outer函数里又定义了一个inner函数,并调用了它。你注意到了吗? 内函数在自己作用域内查找局部变量失败后,会进一步向上一层作用域里查找。
def outer():
x = 1
def inner():
y = x + 1
print(y)
inner()
outer() #输出结果 2
如果我们在外函数里不直接调用内函数,而是通过return inner返回一个内函数的引用 这时会发生什么呢? 你将会得到一个内函数对象,而不是运行结果。
def outer():
x = 1
def inner():
y = x + 1
print(y)
return inner
outer() # 输出<function outer.<locals>.inner at 0x039248E8>
f1 = outer()
f1() # 输出 2
上述这个案例比较简单,因为outer和inner函数都是没有参数的。我们现在对上述代码做点改动,加入参数。你可以看到外函数的参数或变量可以很容易传递到内函数。
def outer(x):
a = x
def inner(y):
b = y
print(a+b)
return inner
f1 = outer(1) # 返回inner函数对象
f1(10) # 相当于inner(10)。输出11
如果上例中外函数的变量x换成被装饰函数对象(func),内函数的变量y换成被装饰函数的参数,我们就可以得到一个通用的装饰器啦(如下所示)。你注意到了吗? 我们在没对func本身做任何修改的情况下,添加了其它功能, 从而实现了对函数的装饰
def decorator(func):
def inner(*args, **kwargs):
add_other_actions()
return func(*args, **kwargs)
return inner
请你仔细再读读上面这段代码,我们的decorator返回的仅仅是inner函数吗? 答案是不。它返回的其实是个闭包(Closure)。整个装饰器的工作都依赖于Python的闭包原理。
闭包(Closure)
闭包是Python编程一个非常重要的概念。如果一个外函数中定义了一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。
例如如下函数
def outer(x):
a = x
def inner(y):
b = y
print(a+b)
return inner
f1 = outer(1) # 返回inner函数对象+局部变量1(闭包)
f1(10) # 相当于inner(10)。输出11
一般一个函数运行结束的时候,临时变量会被销毁。但是闭包是一个特别的情况。当外函数发现,自己的临时变量会在将来的内函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量同内函数绑定在一起。这样即使外函数已经结束了,内函数仍然能够使用外函数的临时变量。这就是闭包的强大之处。
编写一个通用的装饰器
我们现在可以开始动手写个名为hint的装饰器了,其作用是在某个函数运行前给我们提示。这里外函数以hint命名,内函数以常用的wrapper(包裹函数)命名。
def hint(func):
def wrapper(*args, **kwargs):
print('{} is running...'.format(func.__name__)) # hello is running...
return func(*args, **kwargs)
return wrapper
@hint # 等同于 hint(hello)
def hello():
print("hello")
hello() # 等同于 hint(hello)()
print(hello.__name__) # wrapper
需要注意的是:被装饰器装饰的函数看上去名字没变,其实已经变了。当你在运行 hello() 后你会发现它的名字已经变成 wrapper,这显然不是我们想要的效果。出现这个原因是因为外函数返回是由 外部引用变量组成的闭包。为了解决被装饰器过的函数 _name_ 不变,我们可以使用 functools 模块里的 wraps 方法先对 func 变量进行 wraps。
from functools import wraps
def hint(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('{} is running...'.format(func.__name__))
return func(*args, **kwargs)
return wrapper
@hint # 等同于 hint(hello)
def hello():
print("Hello")
hello()
print(hello.__name__) # hello
到这一步其实我们已经可以写一个比较通用的装饰器,并且能保证被装饰的函数 _name_ 不变,但是使用嵌套函数也有缺点,在结构上不够直观。我们可以借助 python 提供 decorator 模块来优化我们的代码
from decorator import decorator
@decorator
def hint(func, *args, **kwargs):
print('{} is running...'.format(func.__name__))
return func(*args, **kwargs)
@hint # 等同于 hint(hello)
def hello():
print("decorator")
hello()
编写一个带参数的装饰器
前面几个装饰器一般是内外两层嵌套函数。如果我们需要编写的装饰器本身是带参数的,我们需要编写三层的嵌套函数,其中最外一层用来传递装饰器的参数。现在我们要对@hint装饰器做点改进,使其能通过@hint(name=“tom”)传递参数。该装饰器在函数运行前给出提示的时候还显示函数编写人员的名字。完整代码如下:
from functools import wraps
def hint(name):
def wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
print('{} is running...'.format(func.__name__))
print('hint name is {}'.format(name))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@hint('tom')
def hello():
print("decorator")
hello()
基于类实现的装饰器
python 装饰器不仅可以用嵌套函数来编写,同样也可以使用类来编写。调用 _init_ 方法创建实例传递参数,同时调用 _call_ 方法实现对被装饰函数功能添加
- 基于类实现的装饰器不带参数
from functools import wraps
class Hint(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('{} is running...'.format(self.func.__name__))
return self.func(*args, **kwargs)
@Hint
def hello():
print('hello world')
-
基于类实现的装饰器带参数
from functools import wraps class Hint(object): def __init__(self, name): self.func = func def __call__(self, func): def wrapper(*args, **kwargs): print('{} is running...'.format(self.func.__name__)) return func(*args, **kwargs) return wrapper @Hint('Tom') def hello(): print('hello world')