python中的装饰器真的是个很难理解的概念,以下只是我在学习过程中的一点浅见,学习交流。
1 闭包
在谈装饰器之前,不得不说一下闭包的概念。什么是闭包呢?维基百科上是这么讲的——
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
简直不像人话。我们通过一小段代码来感受一下闭包:
def foo():
a = 'free var'
def bar():
print(a)
return bar
代码中有两个函数:外函数foo()以及内函数bar(),这个嵌套函数就形成了一个闭包。闭包是延伸了作用域的函数,它使得内函数bar()能够访问未在其函数体中定义的非全局变量,未在函数定义体中定义的非全局变量一般都是在嵌套函数中出现。
形成闭包一般需要以下条件:
- 在一个外函数中定义一个内函数;
- 外函数的返回值是内函数的引用;
- 内函数里运用了外函数的临时变量。
2 装饰器
装饰器简单来讲是修改其他函数的功能的函数。装饰器也是一个闭包,只不过变量是被装饰的另一个函数。可以在装饰器内部实现额外的功能,以增强被装饰函数的行为,然后返回被装饰的函数或者将其替换成一个新的函数。
2.1 一般装饰器
def wrapper(func):
def inner(*args,**kwargs):
# function code
return func(*args,**kwargs)
return inner
装饰器的语法糖@,使用方式:
@wrapper
def test(var):
pass
运行的过程:
wrapper(test) ===> inner(var) ===> test(var)
2.2 带参数装饰器
有时候装饰器本身需要使用一些参数,这时装饰器在定义时需要三层函数。以下是一个例子:
def logit(logfile='out.log'):
def logging_decorator(func):
def wrapped_function(*args, **kwargs):
# function code
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我们使用@logit(logfile='out.log')调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
2.3 装饰器类
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
def wrapped_function(*args, **kwargs):
# function code
return func(*args, **kwargs)
return wrapped_function
def notify(self):
pass
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法。
2.4 类装饰器
装饰器的参数也可以是一个类,也就是说,装饰器可以装饰类:
import types
def deco(cls):
for key, method in cls.__dict__.items():
if isinstance(method, types.FunctionType):
print(key, ':', method.__name__)
return cls
@deco
class Test:
def __init__(self):
pass
def foo(self):
pass
2.5 装饰器的其他问题
1.装饰器可以叠加使用,一个被装饰的函数可以使用多个装饰器。
@wrapper1
@wrapper2
def func()
pass
等效于 f = wrapper1( wrapper2( func ) )
2.装饰器是导入时运行的,而被装饰的函数是明确调用时运行的。
3.自定义的装饰器会改变被装饰函数的元信息,如docstring,__name__等,我们可以使用标准库functools中的wraps函数来解决这个问题。wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
# function code
return f(*args, **kwargs)
return decorated
参考文章: