Python装饰器有哪些常见用途?[关闭]


336

尽管我喜欢将自己视为一个相当称职的Python编码器,但我从来没有想到过的语言的一方面是装饰器。

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

因此,我想知道你们是否可以提供一些示例,说明您在自己的程序中使用装饰器的位置,并希望我会收到“ A-ha!”字样。片刻,他们。


5
另外,装饰器对于记忆非常有用-即缓存功能的计算缓慢结果。装饰器可以返回检查输入的函数,如果已经显示输入,则返回缓存的结果。
彼得

1
请注意functools.lru_cache,自2011
。– Taegyung

Python Decorator库的内容应该使您对它们的其他用途有所了解。
martineau

Answers:


126

我将装饰器主要用于计时目的

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):
    ...

13
在Unix下,time.clock()测量CPU时间。time.time()如果要测量挂钟时间,则可能要改用。
雅巴

20
很好的例子!不知道它做什么。一个很好的解释是您在这里所做的事情以及装饰器如何解决问题。
MeLight 2014年

7
好吧,它可以衡量myFunction跑步所需的时间...
RSabet

98

我已使用它们进行同步。

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版本开始)对象结合使用, 以将装饰器的实现简化为:

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()放在函数调用的任一侧。


17
可能是合理的,但装饰器本质上是令人困惑的,尤其是。向那些在您身后并尝试修改您的代码的一年级新手。简单地避免这种情况:只需将do_something()的代码放在“ with lock:”下的代码块中,每个人都可以清楚地看到您的目的。装饰器被想看起来很聪明的人过度使用(实际上很多人都如此),但是随后这些代码变成了凡人,并被精疲力尽。
凯文·赖斯

18
@ KevinJ.Rice限制您的代码,以便“第一年的菜鸟”能更好地理解它是一种可怕的做法。Decorator语法更容易阅读,并且极大地分离了代码。
TaylerJones

18
@TaylerJones,编写代码时,代码的可读性是我的最高优先事项。每次修改代码,都会读取7次以上。难以理解的代码(对于菜鸟或在时间压力下工作的专家而言)是每次有人访问源代码树都必须支付的技术债务。
凯文·赖斯

@TaylerJones对于程序员来说,最重要的任务之一就是要提供清晰度。
JDOaktown

71

我使用装饰器进行类型检查参数,这些参数通过一些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()为我完成所有工作。


15
对于有兴趣的人,还有的实现@accepts在PEP 318
马蒂诺

2
我认为有错别字..第一种方法应该是accepts ..您声明两者均为“ myMethod”
DevC

1
@DevC不,它看起来像错字。由于这显然不是“ accepts(..)”的实现,因此此处的“ accepts(..)”执行了否则将由“ myMethod(..)”开头的两行完成的工作—只有合适的解释。
Evgeni Sergeev 2015年

1
抱歉,我只是想指出一下,检查传递的参数的类型并引发TypeError,这是一种不好的做法,因为如果它仅检查浮点数,则不会接受例如int,并且通常代码本身应适应传递的不同类型的值,以实现最大的灵活性。
Gustavo6046 '16

2
在Python中建议进行类型检查的推荐方法是通过内置isinstance()函数,就像在装饰器的PEP 318 实现中所做的那样。由于其classinfo参数可以是一种或多种类型,因此使用它还将减轻@ Gustavo6046的(有效)反对意见。Python也有一个Number抽象基类,因此可以进行非常通用的测试isinstance(42, numbers.Number)
martineau

48

装饰器用于您要透明“包装”其他功能的任何物品。

Django使用它们将“需要登录”功能包装在视图功能上,以及注册过滤器功能

您可以使用类装饰器将命名日志添加到类

您可以“掌握”现有类或功能行为的任何足够通用的功能,对于装饰来说都是公平的。

PEP 318指向的Python-Dev新闻组上讨论了用例-函数和方法的装饰器


Cherrypy使用@ cherrypy.expose保持公开的功能和隐藏的功能。那是我的第一个介绍,我在那里习惯了。
Marc Maxmeister 2015年

26

对于鼻子测试,您可以编写一个装饰器,该装饰器为单元测试功能或方法提供几组参数:

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

23

Twisted库结合使用装饰器和生成器,给人一种异步函数是同步的错觉。例如:

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

使用此功能,原本可以分解成许多小回调函数的代码可以很自然地作为一个单独的块编写,从而使理解和维护变得更加容易。


14

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

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()

10

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


6

我正在使用以下装饰器来使函数成为线程安全的。它使代码更具可读性。它几乎与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

1
这是否意味着每个经过如此装饰的功能都有其自己的锁?
悲伤

1
@grieve是的,每次使用装饰器(调用)时,都会为要装饰的函数/方法创建一个新的锁定对象。
martineau 2010年

5
真的很危险 inc_var()方法是“线程安全的”,因为一次只有一个人可以调用它。就是说,由于该方法对成员变量“ var”进行操作,并且可能其他方法也可能对成员变量“ var”进行操作,并且由于未共享锁,因此这些访问不是线程安全的。以这种方式进行操作会使X类用户产生错误的安全感。
Bob Van Zant 2013年

在使用单锁之前,那不是线程安全的。
昌都

5

装饰器用于定义函数的属性或用作更改函数的样板。对于他们来说,返回完全不同的功能是可能的,但违反直觉。在这里查看其他响应,似乎最常见的用途之一是限制其他过程的范围-日志记录,性能分析,安全检查等。

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())

这不是真的。装饰器可以完全改变功能的行为。
递归

好的。但是装饰器“完全改变功能行为”的频率是多少?从我所看到的,当它们不用于指定属性时,它们仅用于样板代码。我已经编辑了我的回复。
Nikhil Chelliah,2009年

5

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


1

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

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'


1

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

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

6
谢谢您的示例,但(重复)我不得不说WTF-您为什么要使用它?它具有使人困惑的巨大潜力。当然,我尊重极端用例的需求,但是您遇到了许多没有经验的Python开发人员遇到的一个常见问题-没有足够地使用类。也就是说,只需具有一个简单的count类,即可对其进行初始化和使用。新手倾向于编写直接(非基于类的代码),并尝试通过精心设计的变通办法来解决类功能的缺乏。请不要 请?不好意思弹奏,谢谢您的回答,但是您为我按下了热键。
凯文·赖斯

如果它作为请求我进行代码审查的请求,我对此将为-1,因此我在此方面也将-1作为优秀的python。
Techdragon

可爱。傻,但是可爱。:)我不介意偶然的函数属性,但是它们在典型的Python代码中是如此罕见,以至于如果我要使用它,我宁愿明确地使用它,而不是将其隐藏在装饰器下。
下午2点
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.