class Calculator:
def __init__(self, n1, n2):
print('[Calculator] __init__() called!!')
self.num1 = n1
self.num2 = n2
cal = Calculator(10, 20)
print(f'cal.num1: {cal.num1}')
print(f'cal.num2: {cal.num2}')
<결과>
[Calculator] __init__() called!!
cal.num1: 10
cal.num2: 20
__init__()
은 클래스의 생성자 함수로, 객체가 생성될 때 자동으로 호출되며 속성을 초기화하는 역할을 한다.
class P_Class:
def __init__(self, p_num1, p_num2):
print('[pClass] __init__()')
self.p_num1 = p_num1
self.p_num2 = p_num2
class C_Class(P_Class):
def __init__(self, c_num1, c_num2):
print('[cClass] __init__()')
self.c_num1 = c_num1
self.c_num2 = c_num2
cls = C_Class(10,20)
<결과>
[cClass] __init__()
❗ [pClass] __init__()
은 호출되지 않음!
자식 클래스는 초기화가 됐지만 부모 클래스는 초기화를 안해서 그렇다.
부모 클래스도 강제로 초기화를 해줘야함!
class P_Class:
def __init__(self, p_num1, p_num2):
print('[pClass] __init__()')
self.p_num1 = p_num1
self.p_num2 = p_num2
class C_Class(P_Class):
def __init__(self, c_num1, c_num2):
print('[cClass] __init__()')
P_Class.__init__(self,c_num1, c_num2) # 여기!
self.c_num1 = c_num1
self.c_num2 = c_num2
cls = C_Class(10,20)
<결과>
[cClass] __init__()
[pClass] __init__()
P_Class.__init__(self,c_num1, c_num2)
이렇게 부모 클래스를 직접 호출해서 사용해도 기능상 문제는 없지만 단점이 있기 때문에 super()
을 써주는 게 좋다.
단점을 아래에 정리해두었다.
class P_Class:
def __init__(self, p_num1, p_num2):
print('[pClass] __init__()')
self.p_num1 = p_num1
self.p_num2 = p_num2
class C_Class(P_Class):
def __init__(self, c_num1, c_num2):
print('[cClass] __init__()')
super().__init__(c_num1, c_num2) # self 생략 가능
self.c_num1 = c_num1
self.c_num2 = c_num2
cls = C_Class(10,20)
<결과>
[cClass] __init__()
[pClass] __init__()
super()
는 다중 상속에서도 안전하게 작동하며, MRO를 자동으로 따른다!
class A:
def __init__(self):
print("A")
class B:
def __init__(self):
print("B")
class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
위처럼 쓰면 A와 B가 직접 호출되기 때문에 Python의 MRO(Method Resolution Order)를 무시하게 된다.
MRO(Method Resolution Order) ?
파이썬에서 상속받은 클래스들 중 어떤 순서로 메서드를 탐색할지 정의하는 규칙
👉 super()
는 MRO를 따라 호출되기 때문에 다중 상속에서 예상 가능한 순서로 부모 메소드가 실행됨!
클래스명이 하드코딩됨
나중에 상속 구조가 바뀌거나 리팩토링할 때 변경 포인트가 많아져서 유지보수가 어려움
동적 상속이 안 됨
class DynamicChild(some_parent_class):
def __init__(self):
some_parent_class.__init__(self) # ← 이거 위험!
이렇게 되면 some_parent_class가 뭔지 미리 모르면 안 되고,
런타임에서 동적으로 상속하거나 테스트용 mock class 쓸 때 깨질 수 있음!
# 중간고사 클래스와 기말고사 클래스를 상속관계로 만들고 각각의 점수를 초기화하자.
# 또한, 총점 및 평균을 반환하는 기능도 만들어보자
class MidExam:
def __init__(self, m_num1, m_num2):
print('[cMidExam] __init__()')
self.mid_eng_score = m_num1
self.mid_math_score = m_num2
def printscore(self):
print(f'english : {self.mid_eng_score}, math : {self.mid_math_score}')
class FinalExam(MidExam):
def __init__(self, m_num1, m_num2, m_num3, m_num4):
print('[cFinalExam] __init__()')
super().__init__(m_num1, m_num2)
self.final_eng_score = m_num3
self.final_math_score = m_num4
def printscore(self):
super().printscore()
print(f'english : {self.final_eng_score}, math : {self.final_math_score}')
def gettotalscore(self):
total = self.final_eng_score + self.final_math_score
total += self.final_eng_score + self.final_math_score
return total
def getavaragescore(self):
avg = self.gettotalscore() / 4
return avg
exam = FinalExam(10,20,30,80)
exam.printscore()
print(f'Total score: {exam.gettotalscore()}')
print(f'Average score: {exam.getavaragescore()}')
<결과>
[cFinalExam] __init__()
[cMidExam] __init__()
english : 10, math : 20
english : 30, math : 80
Total score: 220
Average score: 55.0
__init__()
은 상속받은 부모 클래스에서도 명시적으로 호출해야 작동함super()
사용이 필수!super()
는 MRO를 따르며, 유지보수 및 테스트 환경에서도 안정적이기 때문에