Python装饰符的一些常见用途是什么?

虽然我喜欢认为自己是一个相当称职的Python程序员,但我始终无法理解该语言的一个方面是装饰器。

我知道它们是什么(表面上),我读过教程,例子,关于Stack Overflow的问题,我理解语法,可以自己写,偶尔使用@classmethod和@staticmethod,但我从来没有想过在我自己的Python代码中使用装饰器来解决问题。我从来没有遇到过这样的问题:“嗯……这看起来像是装修师的工作!”

所以,我想知道你们是否可以提供一些在你们自己的程序中使用装饰器的例子,希望我能有一个“啊哈!”的时刻并得到它们。

83048 次浏览

我主要使用它们来调试(打印函数的参数和结果的包装)和验证(例如,检查参数的类型是否正确,或者在web应用程序的情况下,用户是否有足够的权限调用特定的方法)。

装饰器用于您希望透明地“包装”附加功能的任何内容。

Django使用它们来包装视图函数上的“登录必需”功能,以及注册过滤器函数

你可以为向类中添加命名日志使用类装饰器。

任何可以“附加”到现有类或函数行为上的足够通用的功能都可以用作装饰。

还有一个由PEP 318——函数和方法的装饰器指向的在Python-Dev新闻组中讨论用例

Twisted库将装饰器与生成器结合使用,让人误以为异步函数是同步的。例如:

@inlineCallbacks
def asyncf():
doStuff()
yield someAsynchronousCall()
doStuff()
yield someAsynchronousCall()
doStuff()

使用这种方法,原本被分解成大量小回调函数的代码可以很自然地编写为一个块,使其更容易理解和维护。

装饰器既可以用来定义函数的属性,也可以用作修改函数的样板;它们可能返回完全不同的函数,但这是违反直觉的。看看这里的其他响应,最常见的用途之一似乎是限制一些其他进程的范围——无论是日志记录、概要分析、安全检查等等。

CherryPy使用对象调度将url匹配到对象,并最终匹配到方法。这些方法上的装饰符表示CherryPy是否为允许才能使用这些方法。例如,改编自本教程:

class HelloWorld:


...


def secret(self):
return "You shouldn't be here."


@cherrypy.expose
def index(self):
return "Hello world!"


cherrypy.quickstart(HelloWorld())

我使用装饰器进行类型检查参数,这些参数通过一些RMI传递给我的Python方法。因此,与其重复相同的参数计数,一遍又一遍地抛出异常。

例如,不要:

def myMethod(ID, name):
if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
raise BlaBlaException() ...

我只是声明:

@accepts(uint, utf8string)
def myMethod(ID, name):
...

accepts()为我做了所有的工作。

对于nosetests,你可以编写一个装饰器,它提供一个带有几组参数的单元测试函数或方法:

@parameters(
(2, 4, 6),
(5, 6, 11),
)
def test_add(a, b, expected):
assert a + b == expected

我使用它们进行同步。

import functools


def synchronized(lock):
""" Synchronization decorator """
def wrap(f):
@functools.wraps(f)
def newFunction(*args, **kw):
lock.acquire()
try:
return f(*args, **kw)
finally:
lock.release()
return newFunction
return wrap

正如注释中指出的,自Python 2.5起,你可以将with语句与threading.Lock(或自2.6版起的multiprocessing.Lock)对象结合使用,以简化装饰器的实现:

import functools


def synchronized(lock):
""" Synchronization decorator """
def wrap(f):
@functools.wraps(f)
def newFunction(*args, **kw):
with lock:
return f(*args, **kw)
return newFunction
return wrap

不管怎样,你可以这样使用它:

import threading
lock = threading.Lock()


@synchronized(lock)
def do_something():
# etc


@synchronzied(lock)
def do_something_else():
# etc

基本上,它只是把lock.acquire() / lock.release()放在函数调用的两边。

我使用装饰器主要是为了计时

def time_dec(func):


def wrapper(*arg):
t = time.clock()
res = func(*arg)
print func.func_name, time.clock()-t
return res


return wrapper




@time_dec
def myFunction(n):
...

我最近在开发社交网络web应用程序时使用了它们。对于社区/组,我应该给予成员权限来创建新的讨论和回复消息,您必须是该特定组的成员。所以,我写了一个装饰器@membership_required,并把它放在我的视图中需要的地方。

我使用下面的装饰器来使函数线程安全。它使代码更具可读性。它几乎类似于John Fouhy提出的方法,但不同之处在于它只处理一个函数,不需要显式地创建一个锁对象。

def threadsafe_function(fn):
"""decorator making sure that the decorated function is thread safe"""
lock = threading.Lock()
def new(*args, **kwargs):
lock.acquire()
try:
r = fn(*args, **kwargs)
except Exception as e:
raise e
finally:
lock.release()
return r
return new


class X:
var = 0


@threadsafe_function
def inc_var(self):
X.var += 1
return X.var

我使用这个装饰器来修复参数

def fill_it(arg):
if isinstance(arg, int):
return "wan" + str(arg)
else:
try:
# number present as string
if str(int(arg)) == arg:
return "wan" + arg
else:
# This should never happened
raise Exception("I dont know this " + arg)
print "What arg?"
except ValueError, e:
return arg


def fill_wanname(func):
def wrapper(arg):
filled = fill_it(arg)
return func(filled)
return wrapper


@fill_wanname
def get_iface_of(wanname):
global __iface_config__
return __iface_config__[wanname]['iface']

当我重构一些函数时,需要传递参数“wanN”,但在我的旧代码中,我只传递N或“N”

装饰器可以用来轻松地创建函数方法变量。

def static_var(varname, value):
'''
Decorator to create a static variable for the specified function
@param varname: static variable name
@param value: initial value for the variable
'''
def decorate(func):
setattr(func, varname, value)
return func
return decorate


@static_var("count", 0)
def mainCallCount():
mainCallCount.count += 1

一个明显的用途当然是日志记录:

import functools


def log(logger, level='info'):
def log_decorator(fn):
@functools.wraps(fn)
def wrapper(*a, **kwa):
getattr(logger, level)(fn.__name__)
return fn(*a, **kwa)
return wrapper
return log_decorator


# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()