programing tip

파이썬의 super ()는 다중 상속과 어떻게 작동합니까?

itbloger 2020. 9. 29. 07:27
반응형

파이썬의 super ()는 다중 상속과 어떻게 작동합니까?


저는 Python 객체 지향 프로그래밍에 익숙하지 않으며 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 ()와 함께 실행되며 그 이유는 무엇입니까?

그리고 다른 하나를 실행하려면 어떻게해야합니까? Python 메서드 확인 순서 ( MRO ) 와 관련이 있다는 것을 알고 있습니다 .


이것은 Guido 자신의 블로그 게시물 Method Resolution Order (이전 두 번의 시도 포함) 에서 합리적인 양의 세부 사항을 자세히 설명합니다 .

당신의 예에서, Third()호출합니다 First.__init__. Python은 왼쪽에서 오른쪽으로 나열되는 클래스의 부모에서 각 속성을 찾습니다. 이 경우 우리는 __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가 어떻게 구성되는지 보여주는 것입니다. "첫 번째 \ n 두 번째 \ 세 번째"등을 인쇄하기위한 것이 아닙니다 . 물론 예제를 가지고 놀고, super()호출을 추가 하고, 어떤 일이 발생하는지 확인하고, Python의 상속 모델에 대해 더 깊이 이해할 수 있습니다. 하지만 여기서 제 목표는 간단하게 유지하고 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__ 는 "depth-first left-to-right traversal"을 사용하여 계산됩니다 (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에 변수를 명시 적으로 추가하고 수퍼 호출을위한 엔드 포인트가 될 MixIn 인터페이스를 추가해야했습니다.

다음은 A확장 가능한 기본 클래스입니다 및 BC믹스 인 클래스 기능을 제공하는 사람들 모두 f. AB둘 다 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's' __init__()가 호출되지 않을 것이기 때문에 수동으로 호출해야 합니다.


사무용 겉옷

모든 것이 내려 간다고 가정하면 object(그렇지 않은 경우 사용자가 스스로) Python은 클래스 상속 트리를 기반으로 MRO (메서드 확인 순서)를 계산합니다. MRO는 다음 세 가지 속성을 충족합니다.

  • 학급의 아이들은 부모보다 먼저옵니다
  • 왼쪽 부모가 오른쪽 부모보다 먼저
  • 클래스는 MRO에서 한 번만 나타납니다.

그러한 순서가 없으면 Python 오류입니다. 이것의 내부 작용은 계급 조상의 C3 Linerization입니다. 여기에서 모든 내용을 읽어보십시오 : 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

정보 calfzhou의 코멘트 @ , 당신은 일반적으로 사용할 수 있습니다 **kwargs:

온라인 실행 예

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

결과:

A None
B hello
A1 6
B1 5

위치에 따라 사용할 수도 있습니다.

B1(5, 6, b="hello", a=None)

하지만 MRO를 기억해야합니다. 정말 혼란 스럽습니다.

나는 조금 짜증나는 될 수 있지만, 그 사람들이 사용할 때마다 잊어 발견 *args하고 **kwargs는 이러한 '마법 변수'의 몇 정말 유용하고 제정신 사용 중 하나입니다 동안 그들이하는 메소드를 오버라이드 (override) 할 때입니다.


아직 다루지 않은 또 다른 요점은 클래스 초기화를위한 매개 변수를 전달하는 것입니다. 의 대상이 super하위 클래스에 따라 다르기 때문에 매개 변수를 전달하는 유일한 좋은 방법은 모두 함께 패킹하는 것입니다. 그런 다음 의미가 다른 동일한 매개 변수 이름을 사용하지 않도록주의하십시오.

예:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

제공합니다 :

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

__init__매개 변수를보다 직접적으로 할당하기 위해 수퍼 클래스를 직접 호출하는 것은 유혹적이지만 super수퍼 클래스에 호출이 있거나 MRO가 변경되고 클래스 A가 구현에 따라 여러 번 호출 될 경우 실패합니다 .

결론적으로, 협력 상속과 초기화를위한 슈퍼 및 특정 매개 변수가 잘 작동하지 않습니다.


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

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

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

t = Third()

출력은

first 10
second 20
that's it

Third ()를 호출하면 Third에 정의 된 init를 찾습니다 . 그리고 해당 루틴에서 super를 호출하면 First에 정의 된 init 가 호출 됩니다. MRO = [첫 번째, 두 번째]. 이제 First에 정의 된 init 에서 super를 호출하면 MRO를 계속 검색 하고 Second에 정의 된 init를 찾을 수 있으며 super에 대한 모든 호출은 기본 개체 init에 도달합니다 . 이 예제가 개념을 명확히하기를 바랍니다.

First에서 super에 전화하지 않으면. 체인이 중지되고 다음 출력이 표시됩니다.

first 10
that's it

pythonthehardway를 배우면서 실수가 아니라면 내장 함수 인 super ()라는 것을 배웁니다. super () 함수를 호출하면 상속이 부모와 '형제'를 통과하는 데 도움이되고 더 명확하게 볼 수 있습니다. 저는 아직 초보자이지만 python2.7에서이 super () 사용에 대한 경험을 공유하고 싶습니다.

이 페이지의 설명을 읽은 경우 MRO (Method Resolution Order)에 대해 듣게 될 것입니다.이 방법은 사용자가 작성한 함수이며 MRO는 Depth-First-Left-to-Right 체계를 사용하여 검색하고 실행합니다. 그것에 대해 더 많은 연구를 할 수 있습니다.

super () 함수 추가

super(First, self).__init__() #example for class First.

super ()로 여러 인스턴스와 '패밀리'를 연결할 수 있습니다. 그리고 그것은 메소드를 실행하고, 그것들을 살펴보고 당신이 놓치지 않았는지 확인합니다! 그러나, 전후에 그것들을 추가하는 것은 당신이 학습 파이썬 thehardway 연습 44를 완료했는지 알 수있는 차이를 만듭니다. 재미를 시작합시다 !!

아래의 예를 들어 복사하여 붙여넣고 실행할 수 있습니다.

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

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


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

어떻게 실행 되나요? five () 인스턴스는 다음과 같이됩니다. 각 단계는 클래스에서 수퍼 함수가 추가 된 클래스로 이동합니다.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

부모를 찾았고 3, 4까지 계속됩니다 !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

이제 super ()를 사용하는 모든 클래스에 액세스했습니다! 상위 클래스가 발견되어 실행되었으며 이제 상속에서 함수를 계속 해제하여 코드를 완료합니다.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

위 프로그램의 결과 :

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

나를 위해 super ()를 추가하면 파이썬이 내 코딩을 실행하는 방법을 더 명확하게 볼 수 있고 상속이 내가 의도 한 메서드에 액세스 할 수 있는지 확인할 수 있습니다.


@Visionscaper 가 맨 위에 말한 내용 을 추가하고 싶습니다 .

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

이 경우 인터프리터는 복제 되었기 때문에 객체 클래스를 필터링하지 않습니다. 대신 Second가 머리 위치에 나타나고 계층 하위 집합의 꼬리 위치에 나타나지 않기 때문입니다. 개체는 꼬리 위치에만 나타나며 C3 알고리즘에서 우선 순위를 결정하는 강력한 위치로 간주되지 않습니다.

클래스 C, L (C)의 선형화 (mro)는 다음과 같습니다.

  • 클래스 C
  • 플러스 병합
    • 부모 P1, P2, .. = L (P1, P2, ...) 및
    • 부모 P1, P2, ..

Linearised Merge는 순서가 중요하므로 꼬리가 아닌 목록의 헤드로 나타나는 공통 클래스를 선택하여 수행됩니다 (아래에서 명확 해짐).

Third의 선형화는 다음과 같이 계산할 수 있습니다.

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

따라서 다음 코드에서 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 "that's it"

이 방법이 어떻게 해결 될지 분명해집니다.

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

추가 할 수있는 항목, Django rest_framework 및 데코레이터를 사용한 작은 예제가있을 수 있습니다. 이것은 암시 적 질문에 대한 답을 제공합니다 : "왜 내가 이것을 원할까요?"

말했듯이 : 우리는 Django rest_framework를 사용하고 있으며 일반 뷰를 사용하고 있으며 데이터베이스의 각 객체 유형에 대해 객체 목록에 대해 GET 및 POST를 제공하는 하나의 뷰 클래스와 GET을 제공하는 다른 뷰 클래스가 있습니다. 개별 개체에 대한, PUT 및 DELETE.

이제 POST, PUT 및 DELETE를 Django의 login_required로 장식하려고합니다. 이것이 두 클래스 모두에 어떻게 영향을 미치는지 주목하십시오.

솔루션은 다중 상속을 거칠 수 있습니다.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

다른 방법도 마찬가지입니다.

내 구체적인 클래스의 상속 목록에서 LoginToPost이전 ListCreateAPIViewLoginToPutOrDelete이전에 RetrieveUpdateDestroyAPIView. 내 구체적인 수업 get은 장식되지 않은 채로 남아있을 것입니다.

참고 URL : https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance

반응형