[python] 파이썬의 super ()는 다중 상속에서 어떻게 작동합니까?

나는 파이썬 객체 지향 프로그래밍에서 거의 새로 super()왔으며 특히 다중 상속 과 관련하여 함수 (새로운 스타일 클래스)를 이해하는 데 어려움이 있습니다.

예를 들어 다음과 같은 것이 있다면 :

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

내가 얻지 못하는 것은 Third()클래스가 두 생성자 메서드를 상속합니까? 그렇다면 어떤 것이 super ()로 실행될 것이며 왜?

다른 것을 실행하려면 어떻게해야합니까? 파이썬 메소드 분석 순서 ( MRO ) 와 관련이 있다는 것을 알고 있습니다 .



답변

자세한 내용은 Guido 자신의 블로그 게시물 Method Resolution Order (이전에 시도한 두 가지 시도 포함) 에서 합리적으로 자세히 설명되어 있습니다.

귀하의 예에서는 Third()을 호출 First.__init__합니다. 파이썬은 왼쪽에서 오른쪽으로 나열된 클래스의 부모에서 각 속성을 찾습니다. 이 경우 찾고 있습니다 __init__. 따라서 정의하면

class Third(First, Second):
    ...

파이썬은보고 시작 First하면, 그리고 First속성이없는, 그때는 볼 것이다 Second.

상속이 경로를 넘을 때 (예 : First에서 상속 된 경우 Second) 이 상황이 더 복잡해집니다 . 자세한 내용은 위의 링크를 읽으십시오. 그러나 간단히 말해서 Python은 자식 클래스 자체부터 시작하여 상속 목록에 각 클래스가 나타나는 순서를 유지하려고 시도합니다.

예를 들어 다음과 같은 경우

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO는 [Fourth, Second, Third, First].

그런데 파이썬이 일관된 메소드 해결 순서를 찾지 못하면 사용자를 놀라게 할 수있는 동작으로 돌아 가지 않고 예외가 발생합니다.

모호한 MRO의 예를 추가하도록 편집되었습니다.

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

한다 Third의 MRO는 수 [First, Second]또는 [Second, First]? 명백한 기대는 없으며 Python은 오류를 발생시킵니다.

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

편집 : 위의 예제에는 super()호출이 부족하다고 주장하는 사람들이 여러 명 있습니다. 예제 의 요점은 MRO 구성 방법을 보여주는 것입니다. 그들은되어 있지 “처음 \ nsecond \ 셋째”또는 무엇이든을 인쇄하기위한 것. 물론 예제를 가지고 놀거나, super()호출을 추가 하고, 어떤 일이 발생하는지 확인하고, 파이썬의 상속 모델에 대해 더 깊이 이해할 수 있습니다. 그러나 여기서의 목표는 간단하게 유지하고 MRO가 어떻게 구축되는지 보여주는 것입니다. 그리고 내가 설명한대로 작성되었습니다.

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)


답변

귀하의 코드와 다른 답변은 모두 버그가 있습니다. super()협동 서브 클래 싱이 작동하는 데 필요한 처음 두 클래스 의 호출 이 누락되었습니다 .

다음은 고정 버전의 코드입니다.

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

super()호출은 각 단계에서 MRO에서 다음 메소드를 찾으므로 First와 Second도 메소드를 가져야하며 그렇지 않으면의 끝에서 실행이 중지됩니다 Second.__init__().

이것이 내가 얻는 것입니다 :

>>> Third()
second
first
third


답변

파이썬에서 다중 상속 계층 구조에서 super ()를 사용하는 방법에 대해 읽었을 때 즉시 그것을 얻지 못했기 때문에 생명력 이 조금씩 을 정교화 하고 싶었습니다 .

이해해야 할 것은 완전한 상속 계층의 맥락에서 사용 MRO (Method Resolution Ordering) 알고리즘 따라 다음 방법 을 super(MyClass, self).__init__()제공 한다는 것입니다 . __init__

이 마지막 부분은 이해하는 데 중요합니다. 예제를 다시 고려해 봅시다.

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Guido van Rossum의 Method Resolution Order대한이 기사에 따르면 , 해결 순서 __init__는 “깊이 우선 왼쪽에서 오른쪽 순회”를 사용하여 계산됩니다 (Python 2.3 이전).

Third --> First --> object --> Second --> object

마지막 것을 제외하고 모든 중복을 제거하면 다음과 같이 나타납니다.

Third --> First --> Second --> object

Third예를 들어 클래스 의 인스턴스를 인스턴스화 할 때 발생하는 상황을 따르십시오 ( 예 🙂 x = Third().

  1. MRO에 따르면 Third.__init__실행됩니다.
    • 인쇄물 Third(): entering
    • 그런 다음 super(Third, self).__init__()실행되고 MRO First.__init__가 호출됩니다.
  2. First.__init__ 실행합니다.
    • 인쇄물 First(): entering
    • 그런 다음 super(First, self).__init__()실행되고 MRO Second.__init__가 호출됩니다.
  3. Second.__init__ 실행합니다.
    • 인쇄물 Second(): entering
    • 그런 다음 super(Second, self).__init__()실행되고 MRO object.__init__가 호출됩니다.
  4. object.__init__ 실행 (코드에 인쇄 문장이 없음)
  5. 실행은 다시 돌아가서 Second.__init__인쇄Second(): exiting
  6. 실행은 다시 돌아가서 First.__init__인쇄First(): exiting
  7. 실행은 다시 돌아가서 Third.__init__인쇄Third(): exiting

여기에 Third ()를 인스턴스화하면 다음과 같은 결과가 나타납니다.

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

MRO 알고리즘은 복잡한 경우에는 잘 작동하도록 Python 2.3부터 개선되었지만 “깊이 우선 왼쪽에서 오른쪽 순회”+ “중복 제거 마지막 부분에 대한 예상”을 사용하면 대부분의 경우 여전히 작동합니다. 그렇지 않은 경우 의견). Guido의 블로그 게시물을 반드시 읽으십시오!


답변

이것은 Diamond Problem 으로 알려져 있으며 페이지에는 Python에 대한 항목이 있지만 간단히 말해서 Python은 수퍼 클래스의 메소드를 왼쪽에서 오른쪽으로 호출합니다.


답변

이것은 초기화를 위해 다른 변수로 여러 상속을하고 동일한 함수 호출로 여러 MixIn을 갖는 문제를 해결하는 방법입니다. 전달 된 ** kwargs에 변수를 명시 적으로 추가하고 SuperIn의 엔드 포인트가 될 MixIn 인터페이스를 추가해야했습니다.

다음은 A확장 가능한 기본 클래스입니다 및 BC믹스 인 클래스 기능을 제공하는 사람들 모두 f. A그리고 B둘 다 매개 변수 v를 기대 __init__하고 C기대 w합니다. 이 함수 f는 하나의 매개 변수를 사용 y합니다. Q세 클래스에서 상속받습니다. MixInF의 믹스 인 인터페이스입니다 BC.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)


답변

나는 이것이 super()질문에 직접 대답하지는 않는다는 것을 이해 하지만 공유하기에 충분하다고 생각합니다.

상속 된 각 클래스를 직접 호출하는 방법도 있습니다.


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

이 방법으로 그것을 할 경우, 당신은 내가 확신으로 수동으로 각각 호출해야합니다 것을 그냥 노트 First__init__()호출되지 않습니다.


답변

사무용 겉옷

모든 것이 자손이라고 가정하면 object(자신의 것이 아닌 경우) 파이썬은 클래스 상속 트리를 기반으로 MRO (method resolution order)를 계산합니다. MRO는 3 가지 속성을 충족합니다.

  • 수업의 아이들은 부모보다 먼저옵니다
  • 왼쪽 부모는 오른쪽 부모보다 먼저
  • 수업은 MRO에 한 번만 나타납니다.

이러한 순서가 없으면 Python 오류가 발생합니다. 이것의 내부 작업은 클래스 조상의 C3 라이너입니다. 여기에 대한 모든 내용을 읽으십시오.https://www.python.org/download/releases/2.3/mro/

따라서 아래 두 예에서 모두 다음과 같습니다.

  1. 아이
  2. 왼쪽
  3. 권리
  4. 부모의

메소드가 호출되면 MRO에서 해당 메소드가 처음으로 호출됩니다. 해당 메소드를 구현하지 않는 클래스는 건너 뜁니다. 에 대한 모든 호출 super메소드 내에서이 MRO의 메소드의 다음 발생을 호출합니다. 따라서 클래스를 상속 할 순서와 super메소드에서 호출하는 위치가 모두 중요합니다 .

함께 super각 방법에서 제

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() 출력 :

parent
right
left
child

super각 방법의 마지막

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() 출력 :

child
left
right
parent