【问题标题】:How to build a decorator with optional parameters? [duplicate]如何使用可选参数构建装饰器? [复制]
【发布时间】:2011-04-25 07:15:06
【问题描述】:

我想制作一个可以带或不带参数使用的装饰器: 像这样:

class d(object):
    def __init__(self,msg='my default message'):
        self.msg = msg
    def __call__(self,fn):
        def newfn():
            print self.msg
            return fn()
        return newfn

@d('This is working')
def hello():
    print 'hello world !'

@d
def too_bad():
    print 'does not work'

在我的代码中,只有使用带参数的装饰器才有效:如何继续让两者都工作(带参数和不带参数)?

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    我找到了一个例子,你可以使用@trace或@trace('msg1','msg2'):不错!

    def trace(*args):
        def _trace(func):
            def wrapper(*args, **kwargs):
                print enter_string
                func(*args, **kwargs)
                print exit_string
            return wrapper
        if len(args) == 1 and callable(args[0]):
            # No arguments, this is the decorator
            # Set default values for the arguments
            enter_string = 'entering'
            exit_string = 'exiting'
            return _trace(args[0])
        else:
            # This is just returning the decorator
            enter_string, exit_string = args
            return _trace
    

    【讨论】:

    • 我猜它适用于您的情况,也适用于类似情况。但是如果装饰器的参数实际上是一个可调用的呢?您如何区分修饰函数和参数?
    • @ksrini:Ignacio 多年前在他的answer 中指出了这一点。
    • 您也可以通过使用关键字参数(例如@trace(default=...))来解决此问题。
    • 为避免这种歧义和一个不必要的内部闭包嵌套级别,请参阅ForeverWintr 的single-line improvement 和PatBenavente 的similar answer elsewhere。跨度>
    【解决方案2】:

    如果你想给你的装饰器带参数,你需要总是把它作为一个函数来调用:

    @d()
    def func():
        pass
    

    否则,您需要尝试检测参数的差异——换句话说,您需要神奇地猜测调用者的意思。不要创建需要猜测的 API;始终如一地说出你的初衷。

    换句话说,一个函数要么是一个装饰器,要么是一个装饰器工厂;不应该两者兼而有之。

    请注意,如果您只想存储一个值,则无需编写类。

    def d(msg='my default message'):
        def decorator(func):
            def newfn():
                print msg
                return func()
            return newfn
        return decorator
    
    @d('This is working')
    def hello():
        print 'hello world !'
    
    @d()
    def hello2():
        print 'also hello world'
    

    【讨论】:

    • 这可能是一个很好的建议,但正如 Ignacio Vazquez-Abrams 解释的那样,一个函数不能同时做这两个是不正确的。最好在答案中解释一下。
    • @Muhammad:我没说不能,我说不应该。
    • 我明白了。但是,如果对这一点进行更好的解释,答案的价值会更高。只是说。
    • 这是一个比 Eric 提供的真正可选的示例更易于理解的示例。 Python 新手可能会比在这个中更快地迷失在那个中。
    • 我不考虑检查作为“猜测”传递的参数的类型或数量,但确实认为在某些情况下必须通过@d() 而不是通常的@d 记住和使用装饰器糟糕的建议——包括因为它看起来很奇怪/奇怪。
    【解决方案3】:

    如果你不介意使用命名参数,我做了一些类似于你需要的东西:

    def cached_property(method=None, get_attribute=lambda a: '_%s_cached' % (a,)):
        """
        Caches an object's attribute.
    
        Can be used in the following forms:
        @cached_property
        @cached_property()
        @cached_property(get_attribute=lambda x: 'bla')
    
        @param method: the method to memoizes
        @param get_attribute: a callable that should return the cached attribute
        @return a cached method
        """
        def decorator(method):
            def wrap(self):
                private_attribute = get_attribute(method.__name__)
                try:
                    return getattr(self, private_attribute)
                except AttributeError:
                    setattr(self, private_attribute, method(self))
                    return getattr(self, private_attribute)
            return property(wrap)
        if method:
            # This was an actual decorator call, ex: @cached_property
            return decorator(method)
        else:
            # This is a factory call, ex: @cached_property()
            return decorator
    

    这是因为只有一个非关键字参数,被装饰的函数被传递给装饰器。

    请注意,我还使用了传递给装饰函数的参数,在本例中为“self”。

    【讨论】:

      【解决方案4】:

      这会起作用。

      def d(arg):
          if callable(arg):  # Assumes optional argument isn't.
              def newfn():
                  print('my default message')
                  return arg()
              return newfn
          else:
              def d2(fn):
                  def newfn():
                      print(arg)
                      return fn()
                  return newfn
              return d2
      
      @d('This is working')
      def hello():
          print('hello world !')
      
      @d  # No explicit arguments will result in default message.
      def hello2():
          print('hello2 world !')
      
      @d('Applying it twice')
      @d('Would also work')
      def hello3():
          print('hello3 world !')
      
      hello()
      hello2()
      hello3()
      

      输出:

      This is working
      hello world !
      my default message
      hello2 world !
      Applying it twice
      Would also work
      hello3 world !
      

      如果装饰器函数@invocation 未传递任何显式参数,则使用以下def 中定义的函数调用它。如果它是传递的参数,那么首先用它们调用它,然后那个初步调用的结果(它本身也必须是一个可调用的)被调用的函数是定义。无论哪种方式,最后一次或唯一一次调用的返回值都绑定到定义的函数名。

      【讨论】:

      • 太好了,真是个答案!
      【解决方案5】:

      您必须检测装饰器的参数是否为函数,并在这种情况下使用简单的装饰器。然后你需要希望你永远不需要只向参数化装饰器传递一个函数。

      【讨论】:

        猜你喜欢
        • 2014-10-15
        • 1970-01-01
        • 1970-01-01
        • 2019-07-29
        • 2022-08-15
        • 1970-01-01
        • 2012-02-20
        • 2014-03-27
        • 2017-03-07
        相关资源
        最近更新 更多