수업을 꾸미는 방법?


129

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 __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

필자는 실제로 파이썬에서 C # 인터페이스와 같은 것을 수행하는 방법이라고 생각합니다. 내가 생각하는 패러다임을 바꿔야합니다.

답변:


80

나는 당신이 설명한 접근법 대신 서브 클래스를 고려할 수 있다는 개념을 두 번째로 생각합니다. 그러나 특정 시나리오를 모르면 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

생성자를 교체하는 것은 다소 극적인 일이지만 언어는 이러한 종류의 깊은 내성 및 동적 수정을 지원합니다.


고마워, 내가 찾는거야. 모두 특정 멤버를 갖도록 여러 다른 클래스를 수정할 수있는 클래스입니다. 클래스가 공통 ID 클래스에서 상속되지 않는 이유는 ID 버전뿐만 아니라 비 ID 버전의 클래스를 원하기 때문입니다.
Robert Gowland

메타 클래스는 Python2.5 또는 그 이전 버전에서 이와 같은 작업을 수행하는 방법으로 사용되었지만 오늘날에는 훨씬 더 간단한 클래스 데코레이터 (Steven의 답변 참조)를 매우 자주 사용할 수 있습니다.
Jonathan Hartley

203

클래스 데코레이터가 귀하의 문제에 적합한 솔루션인지 여부에 대한 질문 외에도 :

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__.


2
... 질문에 구체적으로 Python 2.5가 언급되어 있지만
Jarret Hardie

5
linesep 혼란에 대해 죄송하지만 코드 샘플은 주석에 엄격하지 않습니다 ... : def addID (original_class) : original_class .__ orig__init__ = original_class .__ init__ def init __ (self, * args, ** kws) : print "decorator" self.id = 9 original_class .__ orig__init __ (self, * args, ** kws) original_class .__ init = init return original_class @addID 클래스 Foo : def __init __ (self) : print "Foo"a = Foo () print a.id
Gerald Senarclens de Grancy

2
@Steven, @Gerald가 맞습니다. 답변을 업데이트 할 수 있다면 그의 코드를 읽는 것이 훨씬 쉬울 것입니다.)
Day

3
@Day : 미리 알림을 보내 주셔서 감사합니다. 그러나 Geralds 코드로 최대 재귀 깊이를 초과했습니다. 나는 작업 버전을 만들려고 노력할 것이다 ;-)
Steven

1
@Day와 @Gerald : 죄송합니다. Gerald의 코드에 문제가 없었습니다. 주석에서 엉망인 코드 때문에 엉망이되었습니다. ;-)
Steven

20

아무도 클래스를 동적으로 정의 할 수 있다고 설명하지 않았습니다. 따라서 서브 클래스를 정의하고 리턴하는 데코레이터를 가질 수 있습니다.

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 (Blckknght의 의견으로 2.6 이상에서 계속 해야하는 이유)에서 사용할 수 있습니다.

class Foo:
    pass

FooId = addId(Foo)

그리고 파이썬 3에서는 이와 같이 (그러나 super()클래스에서 조심스럽게 사용 하십시오) :

@addId
class Foo:
    pass

그래서 당신은 당신의 케이크를 가질 수 먹을 - 상속 데코레이터!


4
파이썬 2.6 이상에서는 데코레이터에서 서브 클래 싱하는 것이 위험 super합니다. 원래 클래스에서 호출을 중단하기 때문 입니다. 호출 된 Foo이름의 메소드가 있으면 이름 이 원래 클래스가 아닌 데코레이터가 리턴 한 서브 클래스에 바인드 되므로 이름 이 무한히 재귀 됩니다 (어떤 이름으로도 액세스 할 수 없음). 파이썬 3의 논증 은이 문제를 피합니다 (나는 그것이 작동 할 수있는 동일한 컴파일러 마술을 통해 가정합니다). Python 2.5 예제에서와 같이 다른 이름으로 클래스를 수동으로 장식하여 문제를 해결할 수도 있습니다. foosuper(Foo, self).foo()Foosuper()
Blckknght

1
응 고마워, 나는 몰랐다 (파이썬 3을 사용한다). 코멘트를 추가합니다.
앤드류 쿡

13

그것은 좋은 습관이 아니며 그 때문에 그렇게 할 수있는 메커니즘이 없습니다. 원하는 것을 달성하는 올바른 방법은 상속입니다.

수업 문서를 살펴보십시오 .

작은 예 :

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__방법과 회원과 함께 모든 것을 가지고 있습니다.


3
내가 원하는 것은 보스가 포함하고있는 클래스에 대해 불가지론자가 된 것 같습니다. 즉, Boss 기능을 적용하려는 수십 가지 클래스가있을 수 있습니다. 이 12 개의 클래스가 Boss로부터 상속 받도록 남겨 두었습니까?
Robert Gowland

5
@Robert Gowland : 그렇기 때문에 파이썬에는 다중 상속이 있습니다. 예, 다양한 부모 클래스에서 다양한 측면을 상속해야합니다.
S.Lott

7
@ S.Lott : 일반적으로 다중 상속은 나쁜 생각입니다. 너무 많은 상속 수준도 나쁩니다. 다중 상속을 피하는 것이 좋습니다.
mpeterson

5
mpeterson : 파이썬에서 다중 상속이이 접근법보다 더 나쁜가요? 파이썬의 다중 상속에 어떤 문제가 있습니까?
Arafangion

2
@Arafangion : 다중 상속은 일반적으로 문제의 경고 신호로 간주됩니다. 복잡한 계층 구조와 따르기 어려운 관계를 만듭니다. 문제 영역이 다중 상속에 적합하다면 (계층 적으로 모델링 할 수 있습니까?) 많은 사람들에게 당연한 선택입니다. 이것은 다중 상속을 허용하는 모든 언어에 적용됩니다.
Morten Jensen

6

상속이 문제에 더 적합하다는 데 동의합니다.

수업을 꾸미는 데이 질문이 정말 편리하다는 것을 알았습니다. 감사합니다.

상속이 파이썬 2.7 (그리고 원래 함수의 docstring 등을 유지하는 @wraps 등) 에서 어떻게 영향을 받는지를 포함하여 다른 답변을 기반으로 한 또 다른 예가 있습니다 .

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_ifdecoratorif condition평가 에만 적용됩니다 True.

getattr, callable테스트 및 @decorate_if경우 장식이 중단되지 않도록 사용되는 foo()방법은 클래스가 장식 되에 존재하지 않습니다.


4

실제로 여기에는 클래스 데코레이터의 구현이 꽤 있습니다.

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

실제로 이것은 꽤 흥미로운 구현이라고 생각합니다. 그것이 장식하는 클래스를 서브 클래 싱하기 때문에, isinstance체크 와 같은 것들에서이 클래스와 똑같이 동작 합니다.

추가 장점이 있습니다. __init__사용자 정의 장고 양식 의 문이 수정되거나 추가되는 것은 드문 일 이 self.fields아니므 로 문제의 클래스에 대해 모든 실행 self.fields완료된 후에 변경 이 발생하는 것이 좋습니다 __init__.

매우 영리한.

그러나 클래스에서는 실제로 장식이 생성자를 변경하여 클래스 데코레이터의 좋은 사용 사례라고 생각하지 않습니다.


0

다음은 클래스의 매개 변수를 반환하는 문제에 대한 답변입니다. 또한 여전히 상속 체인을 존중합니다. 즉 클래스 자체의 매개 변수 만 반환됩니다. 이 기능 get_params은 간단한 예로 추가되었지만 inspect 모듈 덕분에 다른 기능도 추가 할 수 있습니다.

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.