web-dev-qa-db-ja.com

オプションのパラメーターを使用してデコレーターを構築する方法は?

パラメータの有無にかかわらず使用できるデコレータを作成したいと思います:このような何か:

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'

私のコードでは、パラメーター付きのデコレーターの使用のみが機能しています。両方を機能させるにはどうすればよいですか(パラメーターありとパラメーターなし)。

36
Eric

例を見つけました、_@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
_
43
Eric

パラメータをデコレータに渡したい場合は、alwaysを関数として呼び出す必要があります。

@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'
21
Glenn Maynard

名前付き引数の使用に依存しても構わない場合は、必要なものに似たものを作成しました。

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

これが機能するのは、キーワード以外の引数が1つしかなく、修飾された関数がデコレーターに渡されるためです。

装飾された関数に渡された引数(この場合は 'self')も使用したことに注意してください。

13
ricardo

これはうまくいくでしょう。

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  # No explicit arguments.
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で定義された関数を使用して呼び出されます。 isが引数を渡した場合、最初にそれらが呼び出され、次にthat予備呼び出しの結果(それ自体も呼び出し可能でなければならない)が次の関数で呼び出されます定義された。どちらの方法でも、最後の呼び出しまたは唯一の呼び出しの戻り値は、定義された関数名にバインドされます。

7
martineau

デコレータへの引数が関数かどうかを検出し、その場合は単純なデコレータを使用する必要があります。そして、パラメータ化されたデコレータに関数のみを渡す必要がないことを期待する必要があります。