这种情况下装饰器就使用到了闭包。JavaScript中的防抖与节流函数就是这种典型的装饰器行为。新函数一般会使用外部装饰器函数中的变量当做自由变量,对函数作出某种增强行为。
举个例子,我们知道,当Python函数的参数是个可变对象时,会产生意料之外的行为:
def foo(x, y=[]): y.append(x) print(y) foo(1) foo(2) foo(3)
输出:
[1] [1, 2] [1, 2, 3]
这是因为,函数的参数默认值保存在__defaults__属性中,指向了同一个列表:
>>> foo.__defaults__ ([1, 2, 3],)
我们就可以用一个装饰器在函数执行前取出默认值做深复制,然后覆盖函数原先的参数默认值:
import copy def fresh_defaults(func): defaults = func.__defaults__ def deco(*args, **kwargs): func.__defaults__ = copy.deepcopy(defaults) return func(*args, **kwargs) return deco @fresh_defaults def foo(x, y=[]): y.append(x) print(y) foo(1) foo(2) foo(3)
输出:
[1] [2] [3] 接收参数的装饰器装饰器除了可以接受函数作为参数外,还可以接受其他参数。使用方法是:创建一个装饰器工厂,接受参数,返回一个装饰器,再把它应用到被装饰的函数上,语法如下:
def deco_factory(*args, **kwargs): def deco(func): print(args) return func return deco @deco_factory('factory') def foo(): pass
在Web框架中,通常要将URL模式映射到生成响应的view函数,并将view函数注册到某些中央注册处。之前我们曾经实现过一个简单的注册装饰器,只是注册了view函数,却没有URL映射,是远远不够的。
在Flask中,注册view函数需要一个装饰器:
@app.route('/hello') def hello(): return 'Hello, World'
原理就是使用了装饰器工厂,可以简单的模拟一下实现:
class App: def __init__(self): self.view_functions = {} def route(self, rule): def deco(view_func): self.view_functions[rule] = view_func return view_func return deco app = App() @app.route('/') def index(): pass @app.route('/hello') def hello(): pass for rule, view in app.view_functions.items(): print(rule, ':', view.__name__)
输出:
/ : index /hello : hello
还可以使用装饰器工厂来确定view函数可以允许哪些HTTP请求方法:
def action(methods): def deco(view): view.allow_methods = [method.lower() for method in methods] return view return deco @action(['GET', 'POST']) def view(request): if request.method.lower() in view.allow_methods: ...
重叠的装饰器装饰器也是可以重叠使用的:
@d1 @d2 def foo(): pass
等同于:
foo = d1(d2(foo))
类装饰器装饰器的参数也可以是一个类,也就是说,装饰器可以装饰类:
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