虽然我想将自己视为一个相当称职的Python编码人员,但我从来没能理解过的语言的一个方面是装饰器。 ,我已经阅读了有关堆栈溢出的教程,示例和问题,并且了解语法,可以编写自己的语法,偶尔使用@classmethod和@staticmethod,但是我从来没有想到使用装饰器自己解决问题Python代码。我从来没有遇到过这样的问题,我想:“嗯...这看起来像是装饰员的工作!”在您自己的程序中使用了装饰器,希望我会得到一个“哈!”瞬间得到它们。

评论

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

请注意,Python具有内置的装饰器functools.lru_cache,该功能与Peter自2011年2月发布的Python 3.2以来所说的完全一样。

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

#1 楼

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

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


评论


在Unix下,time.clock()测量CPU时间。如果要测量挂钟时间,则可能要使用time.time()来代替。

–贾巴
13年2月4日在17:45

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

– MeLight
2014年6月15日16:00

好吧,它可以衡量myFunction运行所花费的时间...

– RSabet
2015年6月14日在22:12



#2 楼

我已经将它们用于同步。

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

评论


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

–凯文·赖斯(Kevin J. Rice)
2014年11月25日16:40

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

– TaylerJones
15年2月4日在20:02

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

–凯文·赖斯(Kevin J. Rice)
2015年2月5日在20:28

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

– JDOaktown
19年6月23日在20:15

#3 楼

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

评论


对于任何有兴趣的人,在PEP 318中都有@accepts的实现。

–马丁内
2010-09-15在11:23

我认为有错别字..第一种方法应该是accepts ..您都声明为“ myMethod”

– DevC
2014年4月29日在13:05

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

– Evgeni Sergeev
15-10-24在12:23

不好意思,我只想指出检查传递的参数类型并引发TypeError否则被认为是不好的做法,因为它不会接受例如如果仅检查浮点数,则为int类型,并且因为通常代码本身应适应传递的不同类型的值,以实现最大的灵活性。

– Gustavo6046
16年4月16日在21:16

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

–马丁内
19-11-27在0:13



#4 楼

装饰器用于您要透明地“包装”其他功能的任何东西。 >
您可以使用类装饰器将命名日志添加到类中。

任何足以泛化现有类或功能行为的通用功能都是装饰的公平游戏。 br />
PEP 318指向的Python-Dev新闻组上也有用例的讨论-函数和方法的装饰器。

评论


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

– Marc Maxmeister
2015年1月8日在2:16

#5 楼

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

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


#6 楼

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

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


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

#7 楼

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

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


#8 楼

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

#9 楼

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


评论


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

–悲伤
10年6月28日在19:48

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

–马丁内
2010-09-15在11:31

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

–鲍勃·范·赞特(Bob Van Zant)
2013年6月12日16:03



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

– C
16-2-3在7:46

#10 楼

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

#11 楼

装饰器用于定义函数的属性或用作更改函数的样板。对于他们来说,返回完全不同的功能是可能的,但违反直觉。在这里查看其他响应,似乎最常见的用途之一是限制其他进程的范围-日志记录,性能分析,安全检查等。将网址与对象以及方法进行匹配。这些方法上的装饰器会发出信号,表明是否甚至允许CherryPy使用这些方法。例如,根据本教程改编而成:

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())


评论


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

–递归
09年1月29日,下午3:26

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

– Nikhil Chelliah
09年1月29日在5:34

#12 楼

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

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


评论


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

–凯文·赖斯(Kevin J. Rice)
2014年11月25日下午16:45

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

– Techdragon
17-10-3在2:43

可爱。傻,但是可爱。 :)我不介意偶然的函数属性,但是它们在典型的Python代码中是如此罕见,如果我要使用它,我宁愿明确地使用它,而不是将其隐藏在装饰器下。

– PM 2环
18年5月23日在16:30

#13 楼

我使用此修饰符修复参数仅限