[python] 파이썬에서 ‘슈퍼’는 무엇을합니까?

차이점은 무엇입니까?

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

과:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

super단일 상속만으로 클래스에서 상당히 많이 사용되는 것을 보았습니다 . 왜 다중 상속에 사용하는지 알 수 있지만 이런 상황에서 이점을 사용하는 이점이 무엇인지 확실하지 않습니다.



답변

super()단일 상속 의 이점 은 미미합니다. 대부분 기본 클래스의 이름을 부모 메서드를 사용하는 모든 메서드에 하드 코딩 할 필요는 없습니다.

그러나을 사용하지 않고 다중 상속을 사용하는 것은 거의 불가능합니다 super(). 여기에는 믹스 인, 인터페이스, 추상 클래스 등과 같은 일반적인 관용구가 포함됩니다. 이것은 나중에 사용자를 확장하는 코드로 확장됩니다. 누군가 나중에 확장 Child및 믹스 인 클래스를 작성하려는 경우 코드가 제대로 작동하지 않습니다.


답변

차이점이 뭐야?

SomeBaseClass.__init__(self) 

호출 수단 SomeBaseClass__init__. 동안

super(Child, self).__init__()

인스턴스의 MRO (Method Resolution Order)에 __init__따라 부모 클래스에서 바운드를 호출하는 것을 의미합니다 Child.

인스턴스가 Child의 하위 클래스 인 경우 MRO에서 다음에 오는 다른 상위가있을 수 있습니다.

간단히 설명

클래스를 작성할 때 다른 클래스가 클래스를 사용할 수 있기를 원합니다. super()다른 수업에서 작성중인 수업을 더 쉽게 사용할 수 있습니다.

Bob Martin이 말했듯이 좋은 아키텍처를 사용하면 의사 결정을 가능한 한 오래 연기 할 수 있습니다.

super() 이런 종류의 아키텍처를 가능하게합니다.

다른 클래스가 작성한 클래스를 서브 클래스로 만들면 다른 클래스에서도 상속 될 수 있습니다. 그리고 그 클래스는 메소드 해석을위한 클래스의 순서에 따라 __init__이것 뒤에 오는 것을 가질 수 있습니다 __init__.

super그렇지 않으면 작성중인 클래스의 부모를 하드 코딩 할 것입니다 (예와 같이). 즉 __init__, MRO 에서 다음 을 호출 하지 않으므로 코드를 재사용 할 수 없습니다.

개인적인 용도로 자신의 코드를 작성하는 경우이 구분에 신경 쓰지 않을 수 있습니다. 그러나 다른 사람들이 귀하의 코드를 사용하도록하려면 코드 super사용자에게 더 큰 유연성을 제공하는 것이 한 가지입니다.

파이썬 2 대 3

이것은 Python 2와 3에서 작동합니다.

super(Child, self).__init__()

이것은 Python 3에서만 작동합니다.

super().__init__()

스택 프레임에서 위로 이동하여 메소드에 대한 첫 번째 인수 (일반적으로 self인스턴스 메소드 또는 cls클래스 메소드에 대한-그러나 다른 이름 일 수 있음)를 가져오고 Child자유 변수에서 클래스 (예 :)를 찾아서 인수 없이 작동합니다. __class__메서드에서 이름 을 자유 폐쇄 변수로 조회 합니다).

나는 크로스 호환 방법을 사용하는 방법을 보여주고 super싶지만 Python 3 만 사용하는 경우 인수없이 호출 할 수 있습니다.

순방향 호환성을 통한 간접

무엇을 제공합니까? 단일 상속의 경우 문제의 예는 정적 분석 관점과 실질적으로 동일합니다. 그러나를 사용 super하면 호환성이 뛰어난 간접 계층이 제공됩니다.

숙련 된 개발자에게는 호환성이 매우 중요합니다. 코드를 변경할 때 최소한의 변경으로 코드가 계속 작동하기를 원합니다. 개정 이력을 볼 때 변경 사항을 정확히보고 싶을 것입니다.

단일 상속으로 시작할 수 있지만 다른 기본 클래스를 추가하기로 결정한 경우 기본으로 줄을 변경하면됩니다-상속 한 클래스에서 기본이 변경되면 (mixin이 추가됨) 변경됩니다 이 수업에는 아무것도 없습니다. 특히 Python 2에서는 인수를 전달 super하고 올바른 메소드 인수를 올바르게 얻는 것이 어려울 수 있습니다. super단일 상속으로 올바르게 사용하고 있다는 것을 알고 있다면 디버깅이 덜 어려워집니다.

의존성 주입

다른 사람들이 귀하의 코드를 사용하고 메소드 분석에 부모를 주입 할 수 있습니다.

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

객체에 다른 클래스를 추가하고 테스트 또는 다른 이유로 Foo와 Bar 사이에 클래스를 주입한다고 가정하십시오.

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

사용중인 자식이 자체적으로 호출 할 메소드를 하드 코딩했기 때문에 슈퍼가 아닌 자식을 사용하면 종속성을 주입하지 못합니다.

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

그러나 사용하는 자식이있는 클래스 super는 종속성을 올바르게 주입 할 수 있습니다.

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

의견 해결

세상에서 왜 이것이 유용할까요?

Python은 C3 선형화 알고리즘 을 통해 복잡한 상속 트리를 선형화하여 MRO (Method Resolution Order)를 만듭니다.

우리는 메소드 가 순서대로 조회되기를 원합니다 .

부모에 정의 된 메소드가없이 다음 순서로 다음 메소드를 찾으 super려면

  1. 인스턴스 유형에서 mro를 가져옵니다.
  2. 메소드를 정의하는 유형을 찾으십시오.
  3. 방법으로 다음 유형을 찾으십시오.
  4. 해당 메소드를 바인딩하고 예상 인수로 호출하십시오.

UnsuperChild액세스 할 수 없습니다 InjectMe. “항상 사용하지 마십시오”라는 결론이 나오지 않는 이유는 무엇 super입니까? 내가 여기서 무엇을 놓치고 있습니까?

UnsuperChild않습니다 하지 에 액세스 할 수 있습니다 InjectMe. 에 UnsuperInjector액세스 할 InjectMe수 있지만 상속 된 메소드에서 해당 클래스의 메소드를 호출 할 수 없습니다 UnsuperChild.

두 하위 클래스 모두 MRO에서 다음에 나오는 동일한 이름으로 메소드를 호출하려고 합니다.이 클래스는 언제 작성 되었는지 알지 못하는 다른 클래스 일 수 있습니다 .

super부모의 메서드 를 하드 코딩 하지 않은 메서드는 메서드의 동작이 제한되어 서브 클래스가 호출 체인에 기능을 주입 할 수 없습니다.

하나 와 함께 super 더 큰 유연성을 가지고있다. 메소드의 호출 체인을 가로 채서 기능을 삽입 할 수 있습니다.

해당 기능이 필요하지는 않지만 코드의 하위 클래스가 필요할 수 있습니다.

결론

항상 super하드 코딩하는 대신 부모 클래스를 참조하는 데 사용 하십시오.

당신이 의도하는 것은 구체적으로 자녀가 상속하는 것을 보는 것이 아니라 다음 줄에있는 부모 클래스를 참조하는 것입니다.

사용하지 않으면 super코드 사용자에게 불필요한 제약이 생길 수 있습니다.


답변

나는 조금 연주했고 super(), 우리는 전화 순서를 바꿀 수 있음을 알고 있었다.

예를 들어 다음 계층 구조가 있습니다.

    A
   / \
  B   C
   \ /
    D

이 경우 D의 MRO 는 다음과 같습니다 (Python 3에만 해당).

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

super()메소드 실행 후 호출 하는 클래스를 작성해 봅시다 .

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

따라서 해상도 순서가 MRO와 동일하다는 것을 알 수 있습니다. 그러나 super()메소드의 시작 부분을 호출 할 때 :

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...:
I'm from A
I'm from C
I'm from B
I'm from D

MRO 튜플 순서와 반대로 다른 순서가 있습니다.

    A
   / 
  B  C
    /
    D 

추가 독서를 위해 다음 답변을 권장합니다.

  1. super (큰 계층)를 사용한 C3 선형화 예
  2. 이전 스타일 클래스와 새로운 스타일 클래스 사이의 중요한 동작 변경
  3. 새로운 스타일의 수업에 대한 내부 이야기

답변

이 모든 것이 기본 클래스가 새로운 스타일의 클래스라고 가정하지는 않습니까?

class A:
    def __init__(self):
        print("A.__init__()")

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

Python 2에서는 작동하지 않습니다. class A새로운 스타일이어야합니다. 예 :class A(object)


답변

super()부모의 클래스 메서드, 인스턴스 메서드 또는 정적 메서드의 버전을 확인하기 위해 호출 할 때 우리는 범위를 첫 번째 인수로 사용하는 현재 클래스를 전달하여 해결하려는 부모 범위를 나타냅니다. 두 번째 인수는 해당 범위에 적용하려는 객체를 나타내는 관심 객체입니다.

고려 클래스 계층 A, BC각 클래스를 다음 하나의 부모이며, 어디에서 a, b그리고 c각각의 각각의 인스턴스.

super(B, b)
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c)
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c)
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

super정적 메서드와 함께 사용

예를 들어 방법 super()내에서 __new__()사용

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

설명:

1- __new__()호출하는 클래스에 대한 참조를 첫 번째 매개 변수 로 사용하는 것이 일반적이지만 Python에서는 클래스 메서드로 구현 되지 않고 정적 메서드입니다. 즉, 클래스에 대한 참조는 __new__()직접 호출 할 때 첫 번째 인수로 명시 적으로 전달되어야 합니다.

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- super()부모 클래스에 도달하기 위해 호출 할 때 우리는 자식 클래스 A를 첫 번째 인수로 전달 한 다음 관심 객체에 대한 참조를 전달합니다.이 경우 A.__new__(cls)호출 될 때 전달 된 클래스 참조입니다 . 대부분의 경우 자식 클래스에 대한 참조이기도합니다. 예를 들어 여러 세대 상속의 경우에는 그렇지 않을 수 있습니다.

super(A, cls)

3- 일반적 __new__()으로 정적 super(A, cls).__new__메서드이므로 정적 메서드도 반환하므로이 경우 insterest 객체에 대한 참조를 포함하여 모든 인수를 명시 적으로 제공해야합니다 cls.

super(A, cls).__new__(cls, *a, **kw)

4-없이 같은 일을 super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

super인스턴스 메소드와 함께 사용

예를 들어 super()내부에서 사용__init__()

class A(object):
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

설명:

1- __init__는 인스턴스 메소드입니다. 즉, 첫 번째 인수로 인스턴스에 대한 참조가 필요합니다. 인스턴스에서 직접 호출하면 참조가 암시 적으로 전달되므로이를 지정할 필요가 없습니다.

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- super()안에서 호출 할 때 __init__()우리는 자식 클래스를 첫 번째 인수로, 관심 객체를 두 번째 인수로 전달합니다. 일반적으로 자식 클래스의 인스턴스에 대한 참조입니다.

super(A, self)

3- 호출 super(A, self)은 범위를 해결하고 self이제 부모 클래스의 인스턴스 인 것처럼 적용하는 프록시를 반환합니다 . 프록시라고 부르 자 s. __init__()인스턴스 메소드 이므로 호출 s.__init__(...)은 암시 적으로 self부모의 첫 번째 인수로의 참조를 전달합니다 __init__().

4- super부모의 버전에 인스턴스에 대한 참조를 명시 적으로 전달할 필요 없이 동일한 작업을 수행합니다 __init__().

class A(object):
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

super클래스 메소드와 함께 사용

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

설명:

1- 클래스 메소드를 클래스에서 직접 호출 할 수 있으며 클래스에 대한 참조를 첫 번째 매개 변수로 사용합니다.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- super()클래스 메소드 내에서 부모의 버전으로 해석하기 위해 호출 할 때 , 우리는 현재 자식 클래스를 첫 번째 인수로 전달하여 어느 부모의 범위를 확인하려고하는지, 관심있는 객체를 두 번째 인수로 전달하려고합니다. 범위를 적용하려는 객체를 나타 내기 위해 일반적으로 하위 클래스 자체 또는 하위 클래스 중 하나에 대한 참조입니다.

super(B, cls_or_subcls)

3- 통화 super(B, cls)가 범위로 ​​해결되어에 A적용됩니다 cls. alternate_constructor()클래스 메소드 이므로 호출 super(B, cls).alternate_constructor(...)은 의 버전에 cls대한 첫 번째 인수로의 참조를 암시 적으로 전달합니다.Aalternate_constructor()

super(B, cls).alternate_constructor()

4-를 사용하지 않고 동일한 작업을 수행하려면 바인딩되지 않은 버전 (즉, 명시적인 함수 버전)에 super()대한 참조를 가져와야A.alternate_constructor() 합니다. 단순히 이렇게하면 작동하지 않습니다.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

A.alternate_constructor()메소드는 A첫 번째 인수로 암시 적 참조를 취하기 때문에 작동하지 않습니다 . cls여기 전달되는이 두 번째 인자가 될 것입니다.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)


답변

많은 훌륭한 답변이지만 시각적 학습자에게는 : 먼저 수퍼에 대한 논증으로 탐색 한 다음없이 탐색하십시오.
슈퍼 상속 트리 예제

jack클래스에서 만든 인스턴스 Jack가 있는데, 그림에서 녹색으로 표시된 것처럼 상속 체인을 가지고 있다고 상상해보십시오 . 부름:

super(Jack, jack).method(...)

( jack특정 순서의 상속 트리 ) MRO (Method Resolution Order)를 사용하고 에서 검색을 시작 Jack합니다. 왜 부모 클래스를 제공 할 수 있습니까? 인스턴스에서 검색을 시작 jack하면 인스턴스 메소드를 찾고 요점은 부모 메소드를 찾는 것입니다.

super에 인수를 제공하지 않으면 전달 된 첫 번째 인수와 같은 인수가 클래스이고 self전달 된 두 번째 인수는 self입니다. 이것들은 Python3에서 자동 계산됩니다.

그러나 Jack전달하는 대신의 메소드 를 사용하고 싶지 않다고 가정하여 에서 메소드를 위쪽으로 검색하기 위해 Jack전달할 수 Jen있습니다 Jen.

이는 시간 (폭되지 깊이), 예를 들어 만약에 하나 개의 층을 검색 Adam하고 Sue모두 필요한 방법 중 하나가 Sue먼저 발견 될 것이다.

경우 CainSue모두 필요한 방법을 가지고 있었다 Cain의 방법은 먼저 호출된다. 이것은 코드에서 다음에 해당합니다.

Class Jen(Cain, Sue):

MRO는 왼쪽에서 오른쪽입니다.


답변

여기에 큰 대답이 있지만 super()계층 구조의 다른 클래스에 다른 서명이있는 경우 사용 방법을 다루지 않습니다 … 특히__init__

그 부분에 대답하고 효과적으로 사용하려면 super()내 답변 super ()를 읽고 협력 방법의 서명을 변경하는 것이 좋습니다 .

이 시나리오에 대한 해결책은 다음과 같습니다.

  1. 계층 구조의 최상위 클래스는 다음과 같은 사용자 정의 클래스에서 상속해야합니다 SuperObject.
  2. 클래스가 다른 인수를 사용할 수 있으면 항상 수퍼 함수에받은 모든 인수를 키워드 인수로 전달하고 항상 수락하십시오 **kwargs.
class SuperObject:
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

사용법 예 :

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

산출:

E name=python age=28
C age=28
A
D name=python
B
SuperObject