Python装饰器的使用

装饰器使得函数和方法封装更容易阅读和理解,原始的使用场景是将方法在定义的首部将其定义为classmethod或staticmethod.

在添加装饰器功能前,相应的语法如:

1
2
3
4
5
6
7
8
9
10
11
class MyClass(object):
def _class_foo(cls, blah):
pass
_class_foo = classmethod(_closs_foo)
def _static_foo():
pass
_static_foo = staticmethod(_static_foo)
MyClass._class_foo("blah")
MyClass._static_foo()

当类里面定义的类似函数越多,整个代码的可读性就越差。当出现了装饰器功能后,相应代码变为:

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass(object):
@classmethod
def _class_foo(cls, blah):
pass
@staticmethod
def _static_foo():
pass
MyClass._class_foo("blah")
MyClass._static_foo()

可读性提高了,也便于理解,一目了然。

如何编写装饰器

最简单直接的方法是编写一个函数,接受一个函数作为参数,然后返回一个封装了此参数的子函数,如:

1
2
3
4
5
def mydecoration(function):
def _mydecoration(*args, **kw):
res = funtion(*args, **kw)
return res
return _mydecoration

如果装饰器本身还需要接受参数,则必须使用二级封装,如:

1
2
3
4
5
6
7
def mydecoration(arg1, arg2):
def _mydecoration(function):
def __mydecoration(*args, **kw):
res = function(*args, **kw)
return res
return __mydecoration
return _mydecoration

装饰器的一般用法

装饰器一般用于:

  • 参数检查
  • 缓存
  • 代理
  • 提供上下文

参数检查

如Flask中 @setupmethod 是一个装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def setupmethod(f):
"""Wraps a method so that it performs a check in debug mode if the
first request was already handled.
"""
def wrapper_func(self, *args, **kwargs):
if self.debug and self._got_first_request:
raise AssertionError('A setup function was called after the '
'first request was handled. This usually indicates a bug '
'in the application where a module was not imported '
'and decorators or other functionality was called too late.\n'
'To fix this make sure to import all your view modules, '
'database models and everything related at a central place '
'before the application starts serving requests.')
return f(self, *args, **kwargs)
return update_wrapper(wrapper_func, f)

缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def memo(fn):
cache = {}
miss = object()
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result
return wrapper
@memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)

这里通过缓存将递归计算换成了线性计算,提高了性能。

代理

一般用于Python Web框架,如Flask中 @route 是一个装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def route(self, rule, **options):
"""A decorator that is used to register a view
function for a given URL rule. This does the same
thing as :meth:`add_url_rule` but is intended for
decorator usage::
@app.route('/')
def index():
return 'Hello World'
"""
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator

还有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
def login_required(func):
def wrapped_func(*args, **kwargs):
#pseudocode
if check_user_logged_in:
func(*args, **kwargs)
else:
#redirect_to_login_page
return wrapped_func
@login_required
def show_profile_page():
#profile_page_code

提供上下文

这部分可以用 with 替代,但也可以通过装饰器来做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from threading import RLock
my_lock = RLock
def synchronized(lock):
'''Synchronization decorator.'''
def _synchronized(function)
def _wrap(*args, **kw):
lock.acquire()
try:
return function(*args, **kw)
finally:
lock.release()
return _wrap
return _synchronized
# Example usage:
@synchronized(my_lock)
def test(*args):
pass

参考资料

吴羽舒 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!