【问题标题】:What are some common uses for Python decorators? [closed]Python 装饰器有哪些常见用途? [关闭]
【发布时间】:2010-10-04 03:13:00
【问题描述】:

虽然我喜欢认为自己是一名相当称职的 Python 编码员,但我从未能够理解的语言的一个方面是装饰器。

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

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

【问题讨论】:

  • 此外,装饰器对记忆很有用——即缓存函数的计算缓慢的结果。装饰器可以返回一个检查输入的函数,如果它们已经被呈现,则返回一个缓存的结果。
  • 请注意,自 2011 年 2 月发布的 Python 3.2 以来,Python 有一个内置的装饰器 functools.lru_cache,它与 Peter 所说的完全一样。
  • Python Decorator Library 的内容应该让您了解它们的其他用途。

标签: 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()
  • 很好的例子!虽然不知道它做了什么。解释你在那里做什么,以及装饰器如何解决问题会非常好。
  • 嗯,它测量myFunction 运行所需的时间......
  • @time_decmyFunction = time_dec(myFunction) 的语法糖。剩下的就是标准的python
【解决方案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

正如 cmets 中所指出的,从 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() 放在函数调用的两侧。

【讨论】:

  • 可能是合理的,但装饰器本质上是令人困惑的,尤其是。致那些落后于你并试图修改你的代码的一年级新手。简单地避免这种情况:只需让 do_something() 将其代码包含在 'with lock:' 下的块中,每个人都可以清楚地看到你的目的。装饰器被那些想要看起来很聪明的人过度使用(实际上很多人都是),但随后代码就变成了凡人,并且被搞砸了。
  • @KevinJ.Rice 限制您的代码,以便“一年级新手”能够更好地理解这是一种糟糕的做法。装饰器语法更容易阅读,并且极大地解耦了代码。
  • @TaylerJones,代码可读性几乎是我写作时的最高优先级。每次修改代码都会读取 7 次以上。难以理解的代码(对于新手或在时间压力下工作的专家)是每次有人访问源代码树时都必须支付的技术债务。
  • @TaylerJones 程序员最重要的任务之一是提供清晰性。
  • @JDOaktown 程序员的一项重要任务是真正能够理解他们使用的语言的简单概念..
【解决方案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 的实现。
  • 我认为有错字..第一个方法应该是接受..您将两者都声明为“myMethod”
  • @DevC 不,它看起来不像是错字。因为这显然不是“accepts(..)”的实现,而这里的“accepts(..)”完成了原本由“myMethod(..)”开头的两行代码完成的工作——这就是只有适合的解释。
  • 对不起,我只是想指出检查传递的参数的类型并引发 TypeError 否则被认为是一种不好的做法,因为它不会接受例如如果它只检查浮点数,则为 int,因为通常代码本身应该适应传递的不同类型的值以获得最大的灵活性。
  • 在 Python 中进行类型检查的推荐方法是通过内置的 isinstance() 函数,就像在装饰器的 PEP 318 implementation 中完成的那样。由于其classinfo 参数可以是一种或多种类型,因此使用它还可以减轻@Gustavo6046 的(有效)反对意见。 Python 也有一个 Number 抽象基类,所以像 isinstance(42, numbers.Number) 这样的非常通用的测试是可能的。
【解决方案4】:

装饰器用于任何你想用附加功能透明地“包装”的东西。

Django 使用它们来包装"login required" functionality on view functions,以及registering filter functions

您可以为adding named logs to classes 使用类装饰器。

您可以“附加”到现有类或函数的行为的任何足够通用的功能都是装饰的公平游戏。

还有一个discussion of use cases on the Python-Dev newsgroup 指向PEP 318 -- Decorators for Functions and Methods

【讨论】:

  • Cherrypy 使用@cherrypy.expose 来明确哪些函数是公开的,哪些是隐藏函数。那是我的第一次介绍,我在那里习惯了它。
【解决方案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】:

          装饰器用于定义函数的属性或作为改变它的样板;它们返回完全不同的功能是可能的,但违反直觉。查看此处的其他响应,似乎最常见的用途之一是限制其他一些进程的范围 - 无论是日志记录、分析、安全检查等。

          CherryPy 使用对象分派将 URL 与对象以及最终的方法相匹配。这些方法上的装饰器表明 CherryPy 是否甚至允许使用这些方法。比如改编自the tutorial

          class HelloWorld:
          
              ...
          
              def secret(self):
                  return "You shouldn't be here."
          
              @cherrypy.expose
              def index(self):
                  return "Hello world!"
          
          cherrypy.quickstart(HelloWorld())
          

          【讨论】:

          • 这不是真的。装饰器可以完全改变函数的行为。
          • 好的。但是装饰器多久“完全改变一个函数的行为”?从我所见,当它们不用于指定属性时,它们仅用于样板代码。我已经编辑了我的回复。
          【解决方案10】:

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

          【讨论】:

          • 这是否意味着每个功能,如此装饰,都有自己的锁?
          • @grieve 是的,每次使用(调用)装饰器时,它都会为被装饰的函数/方法创建一个新的锁对象。
          • 这真的很危险。方法 inc_var() 是“线程安全的”,因为一次只有一个人可以调用它。也就是说,由于该方法对成员变量“var”进行操作,并且可能其他方法也可能对成员变量“var”进行操作,并且这些访问不是线程安全的,因为锁不是共享的。以这种方式做事会给 X 类的用户一种虚假的安全感。
          • 在使用单锁之前,这不是线程安全的。
          【解决方案11】:

          我最近在开发社交网络 Web 应用程序时使用了它们。对于社区/群组,我应该授予会员权限以创建新讨论并回复您必须成为该特定群组成员的消息。所以,我写了一个装饰器 @membership_required 并将它放在我认为需要的地方。

          【讨论】:

            【解决方案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 类 var,初始化它,然后使用它。菜鸟倾向于编写直通(非基于类的代码)并尝试通过精心设计的解决方法来应对类功能的缺乏。请不要?请?抱歉,竖琴,谢谢你的回答,但你已经为我按下了热键。
            • 如果它显示为我进行代码审查的拉取请求,我会是 -1,所以我也是 -1 作为好的 python。
            • 可爱。很傻,但是很可爱。 :) 我不介意偶尔出现的函数属性,但它们在典型的 Python 代码中是如此罕见,如果我要使用一个,我宁愿明确地这样做,而不是将其隐藏在装饰器下。
            【解决方案13】:

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

            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'

            【讨论】:

              猜你喜欢
              • 2010-11-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-03-08
              相关资源
              最近更新 更多