web-dev-qa-db-ja.com

クラスを飾る方法は?

Python 2.5、クラスを飾るデコレーターを作成する方法はありますか?具体的には、デコレーターを使用してメンバーをクラスに追加し、コンストラクターを変更して値を取得しますそのメンバー。

次のようなものを探します( 'class Foo:'に構文エラーがあります:

def getId(self): return self.__id

class addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

if __== '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

私が本当に望んでいるのは、PythonのC#インターフェイスのようなことをする方法だと思います。私は私のパラダイムを切り替える必要があると思います。

116
Robert Gowland

私は、あなたが概説したアプローチの代わりにサブクラスを検討したいと思うかもしれないという考えを第二に思います。ただし、特定のシナリオ、YMMVを知らない:-)

あなたが考えているのはメタクラスです。 __new__メタクラスの関数には、クラスの完全な提案された定義が渡され、クラスが作成される前に書き換えることができます。その時点で、新しいコンストラクターのコンストラクターをサブアウトできます。

例:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

コンストラクターの置き換えはおそらく少し劇的ですが、この言語はこの種の深い内省と動的な変更をサポートします。

72
Jarret Hardie

クラスデコレータが問題の正しい解決策であるかどうかという質問とは別に:

Python 2.6以降では、@-syntaxを使用したクラスデコレータがあるため、次のように記述できます。

@addID
class Foo:
    pass

古いバージョンでは、別の方法で実行できます。

class Foo:
    pass

Foo = addID(Foo)

ただし、これは関数デコレータの場合と同じように機能し、デコレータは新しい(または変更された元の)クラスを返す必要があることに注意してください。 addIDデコレータは次のようになります。

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

次に、前述のPythonバージョンに適切な構文を使用できます。

しかし、__init__

192
Steven

クラスを動的に定義できると説明した人はいません。したがって、サブクラスを定義する(および返す)デコレーターを使用できます。

_def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId
_

これはPython 2(2.6+でこれを続けなければならない理由を説明するBlckknghtのコメント))で次のように使用できます。

_class Foo:
    pass

FooId = addId(Foo)
_

そしてPython 3は次のようになります(ただし、クラスではsuper()を使用するように注意してください):

_@addId
class Foo:
    pass
_

ケーキを食べることができますand食べる-継承andデコレーター!

15
andrew cooke

それは良い習慣ではなく、そのためのメカニズムはありません。目的を達成する正しい方法は、継承です。

クラスのドキュメント をご覧ください。

ちょっとした例:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

このようにして、ボスはEmployeeが持つすべてのものと、自分の__init__メソッドと独自のメンバー。

12
mpeterson

実際には、クラスデコレータのかなり良い実装があります:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

実際、これはかなり興味深い実装だと思います。デコレートするクラスをサブクラス化するため、isinstanceチェックなどでこのクラスとまったく同じように動作します。

さらに利点があります:__init__ステートメントのカスタムDjangoを変更または追加するためのフォームself.fieldsしたがって、self.fieldsが発生するafterすべての__init__は、問題のクラスに対して実行されました。

非常に賢い。

ただし、クラスでは実際にデコレーションでコンストラクターを変更する必要がありますが、これはクラスデコレーターの良い使用例ではないと思います。

4
Jordan Reiter

私は、継承が提起された問題により適していることに同意します。

この質問は、クラスを装飾する際に非常に便利でした。ありがとう。

Python 2.7、(および @ wraps 、元の関数のdocstringを維持するなど) ):

_def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...
_

多くの場合、デコレーターにパラメーターを追加します。

_from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()
_

出力:

_@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator
_

関数デコレータを使用しましたが、より簡潔であることがわかりました。クラスを装飾するクラスは次のとおりです。

_class Dec(object):

    def __init__(self, msg):
        self.msg = msg

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
_

これらの括弧をチェックし、装飾されたクラスにメソッドが存在しない場合に機能する、より堅牢なバージョン:

_from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator
_

assertは、括弧なしでデコレータが使用されていないことを確認します。存在する場合、装飾されるクラスは、デコレータのmsgパラメータに渡され、AssertionErrorが発生します。

_@decorate_if_は、decoratorconditionと評価される場合にのみTrueを適用します。

getattrcallableテスト、および_@decorate_if_は、foo()メソッドが存在するクラスに存在しない場合にデコレータが破損しないように使用されます。装飾されています。

3
Chris