답변:
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
사용자에게 더 큰 유연성을 제공하는 것이 한 가지입니다.
이것은 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
려면
에
UnsuperChild
액세스 할 수 없습니다InjectMe
. "항상 사용하지 마십시오"라는 결론이 나오지 않는 이유는 무엇super
입니까? 내가 여기서 무엇을 놓치고 있습니까?
는 UnsuperChild
않습니다 하지 에 액세스 할 수 있습니다 InjectMe
. 에 UnsuperInjector
액세스 할 InjectMe
수 있지만 상속 된 메소드에서 해당 클래스의 메소드를 호출 할 수 없습니다 UnsuperChild
.
두 하위 클래스 모두 MRO에서 다음에 나오는 동일한 이름으로 메소드를 호출하려고 합니다.이 클래스는 언제 작성 되었는지 알지 못하는 다른 클래스 일 수 있습니다 .
super
부모의 메서드 를 하드 코딩 하지 않은 메서드는 메서드의 동작이 제한되어 서브 클래스가 호출 체인에 기능을 주입 할 수 없습니다.
하나 와 함께 super
더 큰 유연성을 가지고있다. 메소드의 호출 체인을 가로 채서 기능을 삽입 할 수 있습니다.
해당 기능이 필요하지는 않지만 코드의 하위 클래스가 필요할 수 있습니다.
항상 super
하드 코딩하는 대신 부모 클래스를 참조하는 데 사용 하십시오.
당신이 의도하는 것은 구체적으로 자녀가 상속하는 것을 보는 것이 아니라 다음 줄에있는 부모 클래스를 참조하는 것입니다.
사용하지 않으면 super
코드 사용자에게 불필요한 제약이 생길 수 있습니다.
list
인터페이스 구현을 하나 더 추가 doublylinkedlist
하면 응용 프로그램이 부드럽게 선택합니다. config.txt
로드 타임에 구현을 도입 하고 연결 하여 예제를 더 구성 가능하게 만들 수 있습니다 . 이것이 올바른 예입니까? 그렇다면 코드를 어떻게 연결합니까? 위키에서 DI의 첫 번째 전진을보십시오. 새로운 구현은 어디에서 구성 할 수 있습니까? 코드에서
InjectMe
. 그러나 의견은 토론을위한 것이 아니므로 채팅에서 다른 사람과 더 자세히 논의하거나 기본 사이트에서 새로운 질문을하는 것이 좋습니다.
__init__
함수에 복잡한 문제가 있습니다. 특히의 서명이 __init__
계층 구조의 클래스마다 다른 경우 . 나는이 측면에 초점을 맞추고 대답 추가 한
나는 조금 연주했고 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
추가 독서를 위해 다음 답변을 권장합니다.
super()
부모의 클래스 메서드, 인스턴스 메서드 또는 정적 메서드의 버전을 확인하기 위해 호출 할 때 우리는 범위를 첫 번째 인수로 사용하는 현재 클래스를 전달하여 해결하려는 부모 범위를 나타냅니다. 두 번째 인수는 해당 범위에 적용하려는 객체를 나타내는 관심 객체입니다.
고려 클래스 계층 A
, B
및 C
각 클래스를 다음 하나의 부모이며, 어디에서 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
대한 첫 번째 인수로의 참조를 암시 적으로 전달합니다.A
alternate_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
먼저 발견 될 것이다.
경우 Cain
와 Sue
모두 필요한 방법을 가지고 있었다 Cain
의 방법은 먼저 호출된다. 이것은 코드에서 다음에 해당합니다.
Class Jen(Cain, Sue):
MRO는 왼쪽에서 오른쪽입니다.
여기에 큰 대답이 있지만 super()
계층 구조의 다른 클래스에 다른 서명이있는 경우 사용 방법을 다루지 않습니다 ... 특히__init__
그 부분에 대답하고 효과적으로 사용하려면 super()
내 답변 super ()를 읽고 협력 방법의 서명을 변경하는 것이 좋습니다 .
이 시나리오에 대한 해결책은 다음과 같습니다.
- 계층 구조의 최상위 클래스는 다음과 같은 사용자 정의 클래스에서 상속해야합니다
SuperObject
.- 클래스가 다른 인수를 사용할 수 있으면 항상 수퍼 함수에받은 모든 인수를 키워드 인수로 전달하고 항상 수락하십시오
**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
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
이것은 이해하기 매우 쉽습니다.
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
좋아, 지금 사용하면 super(Child,self)
어떻게 되나요?
Child 인스턴스가 생성되면 MRO (Method Resolution Order)는 상속을 기준으로 (Child, SomeBaseClass, object) 순서입니다. (SomeBaseClass에 기본 객체를 제외한 다른 부모가 없다고 가정)
전달하여 Child, self
, super
의 MRO에서 검색 self
이 경우 그것의 SomeBaseClass에서, 다음 아이의 프록시 객체를 인스턴스 및 반환,이 오브젝트는 호출 __init__
SomeBaseClass의 방법을. 즉,이 경우 반환 super(SomeBaseClass,self)
하는 프록시 객체는super
object
다중 상속의 경우 MRO에는 많은 클래스가 포함될 수 있으므로 기본적으로 super
MRO에서 검색을 시작할 위치를 결정할 수 있습니다.
다음 코드를 고려하십시오.
class X():
def __init__(self):
print("X")
class Y(X):
def __init__(self):
# X.__init__(self)
super(Y, self).__init__()
print("Y")
class P(X):
def __init__(self):
super(P, self).__init__()
print("P")
class Q(Y, P):
def __init__(self):
super(Q, self).__init__()
print("Q")
Q()
의 변화 생성자 경우 Y
에 X.__init__
, 당신은 얻을 것이다 :
X
Y
Q
그러나를 사용 super(Y, self).__init__()
하면 다음을 얻을 수 있습니다.
X
P
Y
Q
그리고 P
또는 Q
심지어 당신이 쓸 때 당신이 모르는 다른 파일에서 관여 할 수 X
와 Y
. 따라서 기본적으로 super(Child, self)
글을 쓸 때 무엇 을 참조 할지 알지 못하며 class Y(X)
Y의 서명조차 간단합니다 Y(X)
. 그래서 슈퍼가 더 나은 선택이 될 수 있습니다.