如何使用可选参数构建装饰器?

Eri*_*ric 34 python decorator

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

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'
Run Code Online (Sandbox Code Playgroud)

在我的代码中,只使用带参数的装饰器工作:如何继续工作(有和没有参数)?

Eri*_*ric 41

我找到了一个例子,你可以使用@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
Run Code Online (Sandbox Code Playgroud)

  • 您也可以使用关键字参数(例如`@trace(default = ...)`)来解决这个问题. (3认同)
  • 我想它适用于您的情况并且适用于类似的情况。但是如果装饰器的参数实际上是一个可调用的呢?您如何区分修饰函数和参数? (2认同)
  • @ksrini:伊格纳西奥几年前在他的[答案](http://stackoverflow.com/a/3931654/355230)中指出了这一点。 (2认同)

Gle*_*ard 22

如果要将参数带到装饰器,则需要始终将其称为函数:

@d()
def func():
    pass
Run Code Online (Sandbox Code Playgroud)

否则,您需要尝试检测参数的差异 - 换句话说,您需要神奇地猜测调用者的含义.不要创建需要猜测的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'
Run Code Online (Sandbox Code Playgroud)

  • @Muhammad:我没说不能,我说不应该. (4认同)
  • 我不考虑检查作为"猜测"传递的参数的类型或数量,但是确实认为必须通过`@d()`而不是通常的`@d`来记住和使用装饰器,在某些情况下,糟糕的建议 - 包括因为它看起来非常奇怪/奇怪. (3认同)
  • 这可能是一个很好的建议,但正如Ignacio Vazquez-Abrams解释的那样,功能不能同时做到这两点是不正确的.最好在答案中解释一下. (2认同)

ric*_*rdo 13

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

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
Run Code Online (Sandbox Code Playgroud)

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

请注意,我还使用了传递给修饰函数的参数,在本例中为'self'.


mar*_*eau 6

这会起作用。

def d(arg):
    if callable(arg):
        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
def hello2():
    print 'hello2 world !'

@d('Applying it twice')
@d('Would also work')
def hello3():
    print 'hello3 world !'

hello()
hello2()
hello3()

# output
#
# This is working
# hello world !
# my default message
# hello2 world !
# Applying it twice
# Would also work
# hello3 world !
Run Code Online (Sandbox Code Playgroud)

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


Ign*_*ams 5

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