装饰器在Java中是一种设计模式。 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
装饰器在Python中提供类似于注解使用方式,直接使用@装饰器名就是调用已经写好的装饰器对现有方法进行装饰,同时python语言已经内置了大量已经实现好的装饰器。这种便利性是基于Python支持面向函数编程这一特性从而可以简单实现闭包而产生的。
闭包就是能够读取其他函数内部变量的函数,它实现上是一种特殊的代码块,一个函数包裹着一个内嵌函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁,它可以通过参数传递改变变量的作用域。
Python中的一个闭包示例:
def outer(a):
c = 5
def inner():
return a + c + 10
return inner
fun1 = outer(10)
fun2 = outer(20)
fun3 = outer(30)
print(fun1())
print(fun2())
print(fun3())
# 25
# 35
# 45
改变了变量作用域体现在,函数`inner()`可以访问到变量`a`。
在Python中创建一个闭包可以归结为以下三点:
闭包可以被理解为一个只读的对象,你可以给他传递一个属性,但它只能提供给你一个执行的接口,这就牵扯到的另一个特性:惰性求值。
还有另一种用处:需要对某个函数的参数提前赋值的情况,当然在Python中已经有了很好的解决方案functools.parial
, 但是用闭包也能实现。
惰性求值这点,我不是很赞同,下面是我关于闭包的看法。
装饰器用于在一个函数上添加一些额外的操作,比如日志、计时等固定操作,一定程度上可以实现切面编程。Python可以非常简单地使用@装饰器名这种注解方式使用已经写好的装饰器。
Python的装饰器基于闭包实现,以下是一个标准范本:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "func will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return ("func is running")
can_run = True
print(func())
can_run = False
print(func())
装饰器依赖具备以下特点的闭包实现:
以下是一个来自Python cookbook的简单示例:
import time
from functools import wraps
def timethis(func):
"""
Decorator that reports the executions time.
"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(func.__name__, "exeuction time:", end-start, "s")
return res
return wrapper
@timethis
def countdown(n):
"""
counts down
"""
while n > 0:
n -= 1
countdown(100000)
# countdown exeuction time: 0.007203102111816406 s
该例说明了装饰器的便利性,只需要在每一个函数前加上@timethis,就可以实现对函数执行时间进行打印的功能,在没有改变原函数代码的条件下,为原函数增加了计时功能,而且能够对任何函数使用。
看到这里,聪明的你或许已经发现了@wraps
本身就是一个装饰器,那么它的功能是什么呢?
实际上,不加@wraps
,也可以实现装饰器的逻辑。加上@wrap是为了保留被装饰函数的元信息(函数名、文档、注释等)。
# 不使用wraps
>>> countdown.__name__
'wrapper'
>>> countdown.__doc__
>>> countdown.__annotations__
{}
# 使用wraps
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown.__name__
'countdown'
>>> countdown.__doc__
'\n\tCounts down\n\t'
>>> countdown.__annotations__
{'n': <class 'int'>}
>>>
Python不仅支持利用闭包实现装饰器,也支持在类中实现装饰器,实现了装饰器的类可以被称作装饰器类。
在一个类中实现内置的__call__()
和__get__()
两个内置方法,就实现了一个装饰器类,并且能想装饰器那样使用@加装饰器名的方式使用,下面是一个来自python cookbook的示例:
import types
from functools import wraps
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0
def __call__(self, *args, **kwargs):
self.ncalls += 1
print("call times: ", self.ncalls)
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
这里的wraps
一样是用来保留函数元信息的。
装饰器类一样是通过将函数作为参数传递,并作为返回值返回来实现的。
在类里面或者外面都可以像使用普通装饰器那样使用它:
@Profiled
def add(x, y):
return x+y
print(add(3, 4))
print(add(4, 5))
# call times: 1
# 7
# call times: 2
# 9
class Spam:
@Profiled
def bar(self, x):
print(self, x)
你可以通过首字母是否大写来判断一个装饰器是依靠闭包还是类实现的。
更多高级用法可以参见参考文献第3篇。