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

What are some common uses for Python decorators?

虽然我认为自己是一个相当称职的Python编码人员,但我从未尝试过的语言的一个方面是装饰人员。

我知道它们是什么(从表面上看),我读过教程、示例、关于堆栈溢出的问题,并且我了解语法,可以自己编写,偶尔使用@classmethod和@staticmethod,但我从未想到使用decorator来解决我自己的python代码中的问题。我从来没有遇到过这样的问题:"嗯……这看起来像是一个装饰工的工作!"

所以,我想知道你们是否可以提供一些例子,说明你们在自己的程序中在哪里使用过装饰,希望我能有一个"A-ha!"抓住他们。


我主要用装修工来安排时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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):
    ...


我用它们来同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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对象(或multiprocessing.Lock自2.6版以来的对象)结合使用,以将装饰器的实现简化为:

1
2
3
4
5
6
7
8
9
10
11
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

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

1
2
3
4
5
6
7
8
9
10
import threading
lock = threading.Lock()

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

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

基本上,它只是将lock.acquire()lock.release()放在函数调用的任一侧。


我使用decorator来检查类型参数,这些参数通过一些rmi传递给我的python方法。因此,不必重复相同的参数计数,而是一次又一次地引发mumbo jumbo异常

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

我只是宣布

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

并且accepts()为我完成所有工作。


装饰器用于任何需要透明地"包装"并具有附加功能的东西。

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

可以使用类修饰符将命名日志添加到类中。

任何足够通用的功能,你可以"附加"到一个现有的类或函数的行为,都是装饰的公平游戏。

还有一个关于python dev新闻组的用例的讨论,该新闻组由pep318——函数和方法的修饰器所指出。


Twisted库使用与生成器结合的装饰器来产生异步函数是同步的假象。例如:

1
2
3
4
5
6
7
@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

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


对于nosetest,您可以编写一个装饰器,为单元测试函数或方法提供多组参数:

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()

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


我正在使用下面的装饰器来生成函数threadsafe。它使代码更具可读性。它几乎与JohnFouhy提出的方法相似,但区别在于,只处理一个函数,不需要显式地创建一个锁对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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


我最近在社交网络Web应用程序上使用过它们。对于社区/团体,我应该授予会员资格,以便创建新的讨论并回复您必须是该特定团体成员的消息。所以,我写了一个装饰师@membership_required,把它放在我需要的地方。


decorator用于定义函数的属性,或者作为改变函数属性的样板;对于它们来说,返回完全不同的函数是可能的,但这是违反直觉的。查看这里的其他响应,最常见的用途之一似乎是限制其他进程的范围——日志记录、分析、安全检查等。

Cherrypy使用对象调度将URL与对象以及最终的方法相匹配。这些方法上的装饰师会发出信号,表明是否允许Cherrypy使用这些方法。例如,改编自教程:

1
2
3
4
5
6
7
8
9
10
11
12
class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())


可以使用decorator轻松创建函数方法变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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


我用这个修饰器修复参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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"