다중 상속으로 부모 클래스 __init__ 호출, 올바른 방법은 무엇입니까?


174

다중 상속 시나리오가 있다고 가정 해보십시오.

class A(object):
    # code for A here

class B(object):
    # code for B here

class C(A, B):
    def __init__(self):
        # What's the right code to write here to ensure 
        # A.__init__ and B.__init__ get called?

작성하는 두 가지 일반적인 방법있다 C'들 __init__:

  1. (오래된 스타일) ParentClass.__init__(self)
  2. (최신 스타일) super(DerivedClass, self).__init__()

그러나 어느 경우 든 상위 클래스 ( AB) 가 동일한 규칙을 따르지 않으면 코드가 제대로 작동하지 않습니다 (일부 누락되거나 여러 번 호출 될 수 있음).

다시 올바른 방법은 무엇입니까? 그것은하지만, 경우에 "둘 중 하나를 따르 단지 일치"말을 쉽게 A또는 B무엇을 한 후, 제 3 자 라이브러리에서입니까? 모든 부모 클래스 생성자가 호출되고 올바른 순서로 한 번만 호출되도록하는 방법이 있습니까?

편집 : 내가 의미하는 바를 보려면 :

class A(object):
    def __init__(self):
        print("Entering A")
        super(A, self).__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        A.__init__(self)
        B.__init__(self)
        print("Leaving C")

그럼 나는 얻는다 :

Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C

주의 B의 초기화가 두 번 호출됩니다. 만약 내가한다면:

class A(object):
    def __init__(self):
        print("Entering A")
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super(C, self).__init__()
        print("Leaving C")

그럼 나는 얻는다 :

Entering C
Entering A
Leaving A
Leaving C

주의 B의 호출 결코 극복 init로 '. 따라서 내가 상속 한 클래스의 init을 알거나 제어하지 않으면 ( AB) 작성중인 클래스를 안전하게 선택할 수 없습니다 ( C).


답변:


78

두 가지 방법 모두 잘 작동합니다. 이 방법을 사용 super()하면 서브 클래스의 유연성이 향상됩니다.

직접 통화 방식에서 C.__init__모두 호출 할 수 있습니다 A.__init__B.__init__.

사용하는 경우 super(), 클래스는 협력 다중 상속을 위해 설계 될 필요가 어디에 C전화 super를 발동, A'도 호출의 코드 super되는 원용 B의 코드'. 수행 할 수있는 작업에 대한 자세한 내용은 http://rhettinger.wordpress.com/2011/05/26/super-considered-super 를 참조하십시오 super.

[나중에 편집 한 질문에 답변]

따라서 (A와 B)에서 상속받은 클래스의 init를 알거나 제어하지 않으면 내가 쓰고있는 클래스 (C)를 안전하게 선택할 수없는 것 같습니다.

참조 된 문서 쇼에서는 래퍼 클래스의 주위를 추가하여이 상황을 처리하는 AB. "비협조적인 수업을 통합하는 방법"섹션에 잘 나와있는 예제가 있습니다.

여러 상속이 더 쉬워서 Car 및 Airplane 클래스를 쉽게 작성하여 FlyingCar를 얻을 수 있기를 원할 수도 있지만 실제로는 별도로 설계된 구성 요소가 종종 원하는대로 원활하게 맞추기 전에 어댑터 또는 래퍼가 필요합니다.

또 다른 생각 : 다중 상속을 사용하여 기능 구성에 만족하지 않으면 컴포지션을 사용하여 어떤 경우에 어떤 메소드가 호출되는지 완벽하게 제어 할 수 있습니다.


4
아닙니다. B의 init이 super를 호출하지 않으면 super().__init__()접근 방식 을 수행해도 B의 init이 호출되지 않습니다 . 내가 호출하는 경우 A.__init__()B.__init__()(A와 B가 전화를 할 경우 직접 후 super) 나는 B의 init 여러 번 호출되는 얻는다.
Adam Parkin

3
@AdamParkin (편집 된 질문에 관한) : 부모 클래스 중 하나가 super () 와 함께 사용하도록 설계되지 않은 경우 일반적으로 슈퍼 호출 을 추가하는 방식으로 래핑 될 수 있습니다 . 참조 된 기사는 "비협조적 클래스를 통합하는 방법"섹션에서 해결 된 예제를 보여줍니다.
Raymond Hettinger

1
어떻게 든 기사를 읽을 때 해당 섹션을 놓쳤습니다. 정확히 내가 찾던 것. 감사!
Adam Parkin

1
파이썬을 작성하고 (가능하면 3!) 어떤 종류의 상속을 사용하지만 특히 다중을 사용하는 경우 rhettinger.wordpress.com/2011/05/26/super-considered-super읽어야 합니다.
Shawn Mehan

1
우리가 지금까지 가지고있을 것이라고 확신했을 때 왜 우리가 날지 못하는 자동차가 없는지 마침내 알기 때문에 공감.
msouth

65

귀하의 질문에 대한 답변은 매우 중요한 측면에 달려 있습니다. 기본 클래스는 다중 상속을 위해 설계 되었습니까?

세 가지 시나리오가 있습니다.

  1. 기본 클래스는 관련이없는 독립형 클래스입니다.

    기본 클래스가 독립적으로 작동 할 수 있고 서로를 모르는 별도의 엔터티 인 경우 다중 상속을 위해 설계 되지 않았습니다 . 예:

    class Foo:
        def __init__(self):
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar

    중요 : 전화 도받지 Foo않습니다 ! 이것이 코드가 올바르게 작동하지 않는 이유입니다. 파이썬에서 다이아몬드 상속이 작동하는 방식 때문에 기본 클래스가있는 클래스는 호출하면 안됩니다 . 아시다시피 그렇게하면 다중 상속이 중단됩니다. 결국 다른 클래스를 호출 하지 않기 때문 입니다. ( 면책 조항 : 방지 -subclasses 것은 내 개인 추천하고 의미없이 합의 된 파이썬 지역 사회의 합의를 어떤 사람들이 사용하는 것을 선호합니다. 당신은 항상 쓸 수 있다는 주장, 모든 클래스에서 어댑터를 클래스로 작동하지 않는 경우 당신은 기대합니다.)Barsuper().__init__()objectsuper().__init__()__init__object.__init__()super().__init__()objectsuper

    이것은 또한 메소드를 상속 받거나 메소드 object가없는 클래스를 작성해서는 안됨을 의미합니다 __init__. __init__메소드를 전혀 정의하지 않으면 호출하는 것과 같은 효과가 super().__init__()있습니다. 클래스가에서 직접 상속하는 경우 다음 object과 같이 빈 생성자를 추가하십시오.

    class Base(object):
        def __init__(self):
            pass

    어쨌든이 상황에서는 각 부모 생성자를 수동으로 호출해야합니다. 이를 수행하는 두 가지 방법이 있습니다.

    • 없이 super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              Foo.__init__(self)  # explicit calls without super
              Bar.__init__(self, bar)
    • super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              super().__init__()  # this calls all constructors up to Foo
              super(Foo, self).__init__(bar)  # this calls all constructors after Foo up
                                              # to Bar

    이 두 가지 방법은 각각 고유 한 장단점이 있습니다. 사용 super하면 클래스가 종속성 주입 을 지원 합니다. 반면에 실수하기가 더 쉽습니다. 예를 들어 당신이 순서 변경하는 경우 FooBar(같은를 class FooBar(Bar, Foo)), 당신은 업데이트해야 할 것입니다 super일치에 대한 호출을. 없이 super이에 대해 걱정할 필요가 없습니다, 그리고 코드는 훨씬 더 읽을 수 있습니다.

  2. 수업 중 하나는 mixin입니다.

    믹스 인이 있어 클래스입니다 설계 다중 상속와 함께 사용하도록. 즉, 믹스 인이 자동으로 두 번째 생성자를 호출하기 때문에 두 부모 생성자를 수동으로 호출 할 필요가 없습니다. 이번에는 단일 생성자 만 호출하면되므로 super부모 클래스의 이름을 하드 코딩하지 않아도됩니다.

    예:

    class FooMixin:
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar
    
    class FooBar(FooMixin, Bar):
        def __init__(self, bar='bar'):
            super().__init__(bar)  # a single call is enough to invoke
                                   # all parent constructors
    
            # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't
            # recommended because we don't want to hard-code the parent class.

    중요한 세부 사항은 다음과 같습니다.

    • 믹스 인은 super().__init__()수신 한 인수를 호출 하고 전달합니다.
    • 믹스 인의 서브 클래스 상속 첫째 : class FooBar(FooMixin, Bar). 기본 클래스의 순서가 틀리면 mixin의 생성자가 호출되지 않습니다.
  3. 모든 기본 클래스는 협동 상속을 위해 설계되었습니다.

    협동 상속을 위해 설계된 클래스는 믹스 인과 매우 유사합니다. 사용되지 않는 모든 인수를 다음 클래스로 전달합니다. 이전과 마찬가지로 호출 super().__init__()하면 모든 부모 생성자가 체인 호출됩니다.

    예:

    class CoopFoo:
        def __init__(self, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class CoopBar:
        def __init__(self, bar, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.bar = bar
    
    class CoopFooBar(CoopFoo, CoopBar):
        def __init__(self, bar='bar'):
            super().__init__(bar=bar)  # pass all arguments on as keyword
                                       # arguments to avoid problems with
                                       # positional arguments and the order
                                       # of the parent classes

    이 경우 부모 클래스의 순서는 중요하지 않습니다. 우리는 CoopBar처음 부터 상속받을 수도 있지만 코드는 여전히 동일하게 작동합니다. 그러나 모든 인수가 키워드 인수로 전달되기 때문에 사실입니다. 위치 인수를 사용하면 인수 순서가 잘못되기 쉬워 지므로 협동 클래스에서 키워드 인수 만 허용하는 것이 일반적입니다.

    이것은 또한 앞서 언급 규칙에 대한 예외입니다 : 모두 CoopFooCoopBar상속 object,하지만 그들은 여전히 전화 super().__init__(). 그렇지 않은 경우에는 공동 상속이 없습니다.

결론 : 올바른 구현은 상속받는 클래스에 따라 다릅니다.

생성자는 클래스의 공용 인터페이스의 일부입니다. 클래스가 믹스 인으로 또는 공동 상속을 위해 설계된 경우이를 문서화해야합니다. 문서에 이런 종류의 내용이 언급되지 않은 경우 클래스 협력 다중 상속을 위해 설계 되지 않았다고 가정하는 것이 안전합니다 .


2
두 번째 요점이 날 날려 버렸어 나는 실제 수퍼 클래스의 오른쪽에 Mixins를 보았고 믹스 인 클래스가 기대하는 속성을 가지고 있는지 확인할 수 없기 때문에 꽤 느슨하고 위험하다고 생각했습니다. 나는 super().__init__(*args, **kwargs)믹스 인에 장군 을 넣고 그것을 먼저 쓰는 것에 대해 생각하지 않았습니다 . 너무 의미가 있습니다.
Minix

10

의 소스 코드를 제어 할 수있는AB 접근 방식 ( "새 스타일"또는 "이전 스타일")이 작동 합니다 . 그렇지 않으면 어댑터 클래스를 사용해야합니다.

접근 가능한 소스 코드 : "새로운 스타일"의 올바른 사용

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        # Use super here, instead of explicit calls to __init__
        super(C, self).__init__()
        print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C

여기에서 MRO (Method Resolution Order)는 다음을 지시합니다.

  • C(A, B)A먼저 지시합니다 B. MRO는 C -> A -> B -> object입니다.
  • super(A, self).__init__()에서 시작된 MRO 체인을 따라 계속 진행 C.__init__됩니다 B.__init__.
  • super(B, self).__init__()에서 시작된 MRO 체인을 따라 계속 진행 C.__init__됩니다 object.__init__.

이 사례는 다중 상속을 위해 설계 되었다고 말할 수 있습니다.

접근 가능한 소스 코드 : "이전 스타일"의 올바른 사용

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        # Don't use super here.
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        B.__init__(self)
        print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

이후 여기에, MRO는 중요하지 않습니다 A.__init__B.__init__명시 적으로 호출된다. class C(B, A):잘 작동합니다.

이 경우는 이전 스타일과 같이 새 스타일의 다중 상속에 대해 "설계되지"않았지만 다중 상속은 여전히 ​​가능합니다.


자, 어떤 경우 AB타사 라이브러리에서이다 - 즉, 당신의 소스 코드를 통제없는 A과를B ? 짧은 대답 : 필요한 super호출 을 구현하는 어댑터 클래스를 디자인 한 다음 빈 클래스를 사용하여 MRO를 정의해야합니다 ( 레이몬드 Hettinger의 기사super -특히 "비 협조 클래스를 통합하는 방법"섹션 참조).

타사 부모 : A구현하지 않습니다 super. B않습니다

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        super(Adapter, self).__init__()
        print("<- C")

class C(Adapter, B):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

클래스 Adapter구현은 super그 때문에 C때 놀이로 오는 MRO 정의 할 수 있습니다 super(Adapter, self).__init__()실행됩니다.

그리고 다른 방법이라면 어떨까요?

타사 부모 : A구현 super; B하지 않습니다

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        super(Adapter, self).__init__()
        B.__init__(self)
        print("<- C")

class C(Adapter, A):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

실행 순서가 바뀐 것을 제외하고는 여기에서 동일한 패턴이 있습니다 Adapter.__init__. super먼저 전화 한 다음 명시적인 전화를 겁니다. 타사 부모가있는 각 케이스에는 고유 한 어댑터 클래스가 필요합니다.

따라서 내가 상속 한 클래스의 init을 알거나 제어하지 않으면 ( AB) 작성중인 클래스를 안전하게 선택할 수 없습니다 ( C).

당신은 당신이하지 않는 경우 처리 할 수 있지만 제어 의 소스 코드 AB어댑터 클래스를 사용하여를, 당신이해야한다는 사실 을 알고 초기화가 부모 클래스의 구현을 얼마나 super(경우 모두에서) 그렇게하기 위해.


4

Raymond가 그의 답변에서 말했듯이 직접 호출 A.__init__하고 B.__init__제대로 작동하면 코드를 읽을 수 있습니다.

그러나 C클래스와 클래스 간 상속 링크를 사용하지 않습니다 . 해당 링크를 악용하면 일관성이 향상되고 최종 리팩토링이 쉽고 오류 발생이 줄어 듭니다. 이를 수행하는 방법의 예 :

class C(A, B):
    def __init__(self):
        print("entering c")
        for base_class in C.__bases__:  # (A, B)
             base_class.__init__(self)
        print("leaving c")

1
가장 좋은 답변은 imho입니다. 보다 미래에 대비할 때 특히 도움이됨을 발견했습니다
Stephen Ellwood

3

이 기사는 협동 다중 상속을 설명하는 데 도움이됩니다.

http://www.artima.com/weblogs/viewpost.jsp?thread=281127

mro()분석법 해결 순서를 보여주는 유용한 방법에 대해 설명합니다 . 당신이 전화를하여 2 예에서 super에서 Asuper호출은 MRO에서 계속됩니다. 순서의 다음 클래스는 B, 이것이 Binit를 처음으로 부르는 이유 입니다.

공식 파이썬 사이트의 더 많은 기술 기사는 다음과 같습니다.

http://www.python.org/download/releases/2.3/mro/


2

타사 라이브러리의 하위 클래스 클래스를 곱 __init__하면 기본 클래스 프로그래밍 방식에 관계없이 실제로 작동 하는 기본 클래스 메서드 (또는 다른 메서드) 를 호출하는 맹목적인 방법은 없습니다 .

super만드는 것이 가능한 협력 클래스 저자에게 알려 할 필요는 복잡한 다중 상속 나무의 일부로서 메소드를 구현하도록 설계 쓰기 클래스. 그러나 사용하거나 사용하지 않을 수있는 임의의 클래스에서 올바르게 상속하는 데 사용할 수있는 방법은 없습니다 super.

기본적으로 클래스가 super기본 클래스에 대한 직접 호출을 사용하여 서브 클래스 화되도록 설계되었는지 여부는 클래스 의 "공용 인터페이스"의 일부인 특성이므로 문서화해야합니다. 라이브러리 작성자가 예상 한 방식으로 타사 라이브러리를 사용하고 있고 라이브러리에 합리적인 문서가있는 경우 일반적으로 특정 작업을 서브 클래 싱하기 위해 수행해야 할 작업을 알려줍니다. 그렇지 않은 경우 서브 클래 싱중인 클래스의 소스 코드를보고 기본 클래스 호출 규칙이 무엇인지 확인해야합니다. 라이브러리 작성자 예상 하지 못한 방식으로 하나 이상의 써드 파티 라이브러리에서 여러 클래스를 결합하는 경우 지속적으로 수퍼 클래스 메소드 호출하지 못할 수 있습니다.; 클래스 A가 사용하는 계층의 일부 super이고 클래스 B가 super를 사용하지 않는 계층의 일부인 경우 두 옵션 모두 작동하지 않을 수 있습니다. 각각의 특정 사례에 적합한 전략을 찾아야합니다.


@RaymondHettinger 글쎄, 당신은 이미 당신의 대답에 대한 몇 가지 생각을 가진 기사를 작성하고 링크 했으므로 추가 할 것이 많지 않다고 생각합니다. :) 나는 비 슈퍼 사용 클래스를 일반적으로 슈퍼 계층에 적용하는 것이 가능하지 않다고 생각한다. 관련된 특정 수업에 맞는 솔루션을 생각해 내야합니다.
Ben
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.