装饰器定义: 在不希望修改函数的定义,在代码运行期间动态增加函数功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
如何使用装饰器: decorator接受一个函数作为参数,并返回一个函数。 借助Python的@语法,把decorator置于函数的定义处,实现功能扩展的目的。
首先明确函数嵌套以及闭包:
嵌套函数
如果在一个函数的内部还定义了另一个函数(注意: 是定义,不是引用!),这个函数就叫嵌套函数。外部的我们叫它外函数,内部的我们叫他内函数。
如下outer函数里又定义了一个inner函数,并调用了它。内函数在自己作用域内查找局部变量失败后,会进一步向上一层作用域里查找。
def outer(): x = 1 def inner(): y = x + 1 print(y) inner() outer() #输出结果 2
如果在外函数里不直接调用内函数,而是通过return inner返回一个内函数的引用,会得到一个内函数对象,而不是运行结果,如下运行outer() 时,内部 inner函数只是在outer()中定义,没有被调用,不会运行,所以运行outer() 只会执行 return inner;运行f1 = outer()时返回inner,再运行f1()时,相当于:f1() = outer()(),即f1() = (inner)(),f1() = inner(),即运行inner函数
def outer():
x = 1
def inner():
y = 2
print(y)
return inner
outer() # 输出<function outer.<locals>.inner at 0x039248E8>
f1 = outer()
f1() # 输出2
##############################################
def outer(x):
a = x
def inner(y):
b = y
print(b)
return inner
f1 = outer(1) # 返回inner函数对象
f1(10) # 相当于inner(10)。输出10
闭包(closure):
闭包是Python编程一个非常重要的概念。如果一个外函数中定义了一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。如下outer方法返回的只是内函数对象吗? 错。我们的outer函数返回的实际上是一个由inner函数和外部引用变量(a)组成的闭包!
def outer(x):
a = x
def inner(y):
b = y
print(a+b)
return inner
f1 = outer(1) # 返回inner函数对象+局部变量(外部引用变量(a))1(闭包)
f1(10) # 相当于inner(10)。输出11
一般一个函数运行结束的时候,临时变量会被销毁。但是闭包是一个特别的情况。当外函数发现,自己的临时变量会在将来的内函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量同内函数绑定在一起。这样即使外函数已经结束了,内函数仍然能够使用外函数的临时变量。这就是闭包的强大之处。
例子:
运行test() 是装载装饰器的过程,相当于执行了test=dect1(dect2(test)),此时先执行dect2(test),结果是输出aaaa、将func指向函数test、并返回函数two,然后执行dect1(two),结果是输出1111、将func指向函数two、并返回函数one,然后进行赋值。
最终运行运行test() 实际是用函数one替代了函数名test。这时实际上执行的是函数one,运行到函数one()中的func()时执行函数two,再运行到函数two中的func()时执行未修饰的函数test,运行完函数test,再返回函数two,之后再返回函数one。
def dec1(func):
print("1111")
def one():
print("2222")
func()
print("3333")
return one
def dec2(func):
print("aaaa")
def two():
print("bbbb")
func()
print("cccc")
return two
@dec1
@dec2
def test():
print("test test")
test()
输出:
aaaa
1111
2222
bbbb
test test
cccc
3333
**********************************************************************************************
如何编写一个通用的装饰器
我们现在可以开始动手写个名为hint的装饰器了,其作用是在某个函数运行前给我们提示。这里外函数以hint命名,内函数以常用的wrapper(包裹函数)命名。
def hint(func):
def wrapper(*args, **kwargs):
print('{} is running'.format(func.__name__))
return func(*args, **kwargs)
return wrapper
@hint
def hello():
print("Hello!")
我们现在对hello已经进行了装饰,当我们调用hello()时,我们可以看到如下结果。
>>> hello()
hello is running.
Hello!
值得一提的是被装饰器装饰过的函数看上去名字没变,其实已经变了。当你运行hello()后,你会发现它的名字已经悄悄变成了wrapper,这显然不是我们想要的(如下图所示)。这一点也不奇怪,因为外函数返回的是由wrapper函数和其外部引用变量组成的闭包。
>>> hello.__name__
'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
def hello():
print("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(coder="John")传递参数。该装饰器在函数运行前给出提示的时候还显示函数编写人员的名字。完整代码如下所示:
from functools import wraps
def hint(coder):
def wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
print('{} is running'.format(func.__name__))
print('Coder: {}'.format(coder))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@hint(coder="John")
def hello():
print("Hello!")
下面这段代码是一段经典的Python装饰器代码,显示了@cache这个装饰器怎么编写和工作的。它需要使用缓存实例做为一个参数,所以也是三层嵌套函数。
import time
from functools import wraps
# 装饰器增加缓存功能
def cache(instance):
def wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
# 构建key: key => func_name::args::kwargs
joint_args = ','.join((str(x) for x in args))
joint_kwargs = ','.join('{}={}'.format(k, v) for k, v in sorted(kwargs.items()))
key = '{}::{}::{}'.format(func.__name__,joint_args, joint_kwargs)
# 根据key获取结果。如果key已存在直接返回结果,不用重复计算。
result = instance.get(key)
if result is not None:
return result
# 如果结果不存在,重新计算,缓存。
result = func(*args, **kwargs)
instance.set(key, result)
return result
return inner_wrapper
return wrapper
# 创建字典构造函数,用户缓存K/V键值对
class DictCache:
def __init__(self):
self.cache = dict()
def get(self, key):
return self.cache.get(key)
def set(self, key, value):
self.cache[key] = value
def __str__(self):
return str(self.cache)
def __repr__(self):
return repr(self.cache)
# 创建缓存对象
cache_instance = DictCache()
# Python语法糖调用装饰器
@cache(cache_instance)
def long_time_func(x):
time.sleep(x)
return x
# 调用装饰过函数
long_time_func(3)
基于类实现的装饰器
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)
#类的装饰器写法, 带参数
class Hint(object):
def __init__(self, coder=None):
self.coder = coder
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('{} is running'.format(func.__name__))
print('Coder: {}'.format(self.coder))
return func(*args, **kwargs) # 正式调用主要处理函数
return wrapper
小结
本文总结了什么是Python的装饰器及其工作原理,并重点介绍了嵌套函数和闭包原理。最后详细展示了如何编写一个通用装饰器及带参数的高级装饰器, 包括使用类来编写装饰器。大家要熟练掌握哦。看不懂的可以先加入微信收藏以后再反复阅读。