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

时间:2010-10-14 09:01:44

标签: 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'

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

5 个答案:

答案 0 :(得分:38)

我找到了一个示例,您可以使用@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

答案 1 :(得分:20)

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

@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'

答案 2 :(得分:11)

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

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”。

答案 3 :(得分:5)

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

答案 4 :(得分:4)

这样可行。

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 !

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