다중 상속 시나리오가 있다고 가정 해보십시오.
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__
:
- (오래된 스타일)
ParentClass.__init__(self)
- (최신 스타일)
super(DerivedClass, self).__init__()
그러나 어느 경우 든 상위 클래스 ( A
및 B
) 가 동일한 규칙을 따르지 않으면 코드가 제대로 작동하지 않습니다 (일부 누락되거나 여러 번 호출 될 수 있음).
다시 올바른 방법은 무엇입니까? 그것은하지만, 경우에 “둘 중 하나를 따르 단지 일치”말을 쉽게 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을 알거나 제어하지 않으면 ( A
및 B
) 작성중인 클래스를 안전하게 선택할 수 없습니다 ( C
).
답변
두 가지 방법 모두 잘 작동합니다. 이 방법을 사용 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)를 안전하게 선택할 수없는 것 같습니다.
참조 된 문서 쇼에서는 래퍼 클래스의 주위를 추가하여이 상황을 처리하는 A
과 B
. “비협조적인 수업을 통합하는 방법”섹션에 잘 나와있는 예제가 있습니다.
여러 상속이 더 쉬워서 Car 및 Airplane 클래스를 쉽게 작성하여 FlyingCar를 얻을 수 있기를 원할 수도 있지만 실제로는 별도로 설계된 구성 요소가 종종 원하는대로 원활하게 맞추기 전에 어댑터 또는 래퍼가 필요합니다.
또 다른 생각 : 다중 상속을 사용하여 기능 구성에 만족하지 않으면 컴포지션을 사용하여 어떤 경우에 어떤 메소드가 호출되는지 완벽하게 제어 할 수 있습니다.
답변
귀하의 질문에 대한 답변은 매우 중요한 측면에 달려 있습니다. 기본 클래스는 다중 상속을 위해 설계 되었습니까?
세 가지 시나리오가 있습니다.
-
기본 클래스는 관련이없는 독립형 클래스입니다.
기본 클래스가 독립적으로 작동 할 수 있고 서로를 모르는 별도의 엔터티 인 경우 다중 상속을 위해 설계 되지 않았습니다 . 예:
class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar
중요 : 전화 도받지
Foo
않습니다 ! 이것이 코드가 올바르게 작동하지 않는 이유입니다. 파이썬에서 다이아몬드 상속이 작동하는 방식 때문에 기본 클래스가있는 클래스는 호출하면 안됩니다 . 아시다시피 그렇게하면 다중 상속이 중단됩니다. 결국 다른 클래스를 호출 하지 않기 때문 입니다. ( 면책 조항 : 방지 에 -subclasses 것은 내 개인 추천하고 의미없이 합의 된 파이썬 지역 사회의 합의를 어떤 사람들이 사용하는 것을 선호합니다. 당신은 항상 쓸 수 있다는 주장, 모든 클래스에서 어댑터를 클래스로 작동하지 않는 경우 당신은 기대합니다.)Bar
super().__init__()
object
super().__init__()
__init__
object.__init__()
super().__init__()
object
super
이것은 또한 메소드를 상속 받거나 메소드
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
하면 클래스가 종속성 주입 을 지원 합니다. 반면에 실수하기가 더 쉽습니다. 예를 들어 당신이 순서 변경하는 경우Foo
와Bar
(같은를class FooBar(Bar, Foo)
), 당신은 업데이트해야 할 것입니다super
일치에 대한 호출을. 없이super
이에 대해 걱정할 필요가 없습니다, 그리고 코드는 훨씬 더 읽을 수 있습니다. -
-
수업 중 하나는 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의 생성자가 호출되지 않습니다.
- 믹스 인은
-
모든 기본 클래스는 협동 상속을 위해 설계되었습니다.
협동 상속을 위해 설계된 클래스는 믹스 인과 매우 유사합니다. 사용되지 않는 모든 인수를 다음 클래스로 전달합니다. 이전과 마찬가지로 호출
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
처음 부터 상속받을 수도 있지만 코드는 여전히 동일하게 작동합니다. 그러나 모든 인수가 키워드 인수로 전달되기 때문에 사실입니다. 위치 인수를 사용하면 인수 순서가 잘못되기 쉬워 지므로 협동 클래스에서 키워드 인수 만 허용하는 것이 일반적입니다.이것은 또한 앞서 언급 규칙에 대한 예외입니다 : 모두
CoopFoo
와CoopBar
상속object
,하지만 그들은 여전히 전화super().__init__()
. 그렇지 않은 경우에는 공동 상속이 없습니다.
결론 : 올바른 구현은 상속받는 클래스에 따라 다릅니다.
생성자는 클래스의 공용 인터페이스의 일부입니다. 클래스가 믹스 인으로 또는 공동 상속을 위해 설계된 경우이를 문서화해야합니다. 문서에 이런 종류의 내용이 언급되지 않은 경우 클래스 가 협력 다중 상속을 위해 설계 되지 않았다고 가정하는 것이 안전합니다 .
답변
및 의 소스 코드를 제어 할 수있는A
B
접근 방식 ( “새 스타일”또는 “이전 스타일”)이 작동 합니다 . 그렇지 않으면 어댑터 클래스를 사용해야합니다.
접근 가능한 소스 코드 : “새로운 스타일”의 올바른 사용
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):
잘 작동합니다.
이 경우는 이전 스타일과 같이 새 스타일의 다중 상속에 대해 “설계되지”않았지만 다중 상속은 여전히 가능합니다.
자, 어떤 경우 A
와 B
타사 라이브러리에서이다 – 즉, 당신의 소스 코드를 통제없는 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을 알거나 제어하지 않으면 (
A
및B
) 작성중인 클래스를 안전하게 선택할 수 없습니다 (C
).
당신은 당신이하지 않는 경우 처리 할 수 있지만 제어 의 소스 코드 A
및 B
어댑터 클래스를 사용하여를, 당신이해야한다는 사실 을 알고 초기화가 부모 클래스의 구현을 얼마나 super
(경우 모두에서) 그렇게하기 위해.
답변
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")
답변
이 기사는 협동 다중 상속을 설명하는 데 도움이됩니다.
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
mro()
분석법 해결 순서를 보여주는 유용한 방법에 대해 설명합니다 . 당신이 전화를하여 2 예에서 super
에서 A
의 super
호출은 MRO에서 계속됩니다. 순서의 다음 클래스는 B
, 이것이 B
init를 처음으로 부르는 이유 입니다.
공식 파이썬 사이트의 더 많은 기술 기사는 다음과 같습니다.
답변
타사 라이브러리의 하위 클래스 클래스를 곱 __init__
하면 기본 클래스 프로그래밍 방식에 관계없이 실제로 작동 하는 기본 클래스 메서드 (또는 다른 메서드) 를 호출하는 맹목적인 방법은 없습니다 .
super
만드는 것이 가능한 협력 클래스 저자에게 알려 할 필요는 복잡한 다중 상속 나무의 일부로서 메소드를 구현하도록 설계 쓰기 클래스. 그러나 사용하거나 사용하지 않을 수있는 임의의 클래스에서 올바르게 상속하는 데 사용할 수있는 방법은 없습니다 super
.
기본적으로 클래스가 super
기본 클래스에 대한 직접 호출을 사용하여 서브 클래스 화되도록 설계되었는지 여부는 클래스 의 “공용 인터페이스”의 일부인 특성이므로 문서화해야합니다. 라이브러리 작성자가 예상 한 방식으로 타사 라이브러리를 사용하고 있고 라이브러리에 합리적인 문서가있는 경우 일반적으로 특정 작업을 서브 클래 싱하기 위해 수행해야 할 작업을 알려줍니다. 그렇지 않은 경우 서브 클래 싱중인 클래스의 소스 코드를보고 기본 클래스 호출 규칙이 무엇인지 확인해야합니다. 라이브러리 작성자 가 예상 하지 못한 방식으로 하나 이상의 써드 파티 라이브러리에서 여러 클래스를 결합하는 경우 지속적으로 수퍼 클래스 메소드 를 호출하지 못할 수 있습니다.; 클래스 A가 사용하는 계층의 일부 super
이고 클래스 B가 super를 사용하지 않는 계층의 일부인 경우 두 옵션 모두 작동하지 않을 수 있습니다. 각각의 특정 사례에 적합한 전략을 찾아야합니다.