본문으로 건너뛰기

상속

파이썬 목차클래스레슨 56 / 59

목차

이미 만들어 둔 클래스를 바탕으로 조금만 다른 새 클래스를 만들고 싶을 때 **상속(inheritance)**을 씁니다. **class 자식클래스(부모클래스):**로 정의하면 부모 클래스의 메서드를 그대로 물려받고, 바꿀 메서드만 자식 클래스에서 다시 정의합니다.

단골 손님용 VipCard는 일반 StampCard와 주인 이름·스탬프 개수를 담는 구조는 똑같지만, add()를 부를 때 스탬프가 1개가 아니라 2개씩 올라갑니다. 처음부터 코드를 다시 다 복사해 적는 대신 StampCard를 상속받아 달라지는 add()만 적어 봅시다.

8단계 중 1단계. 자식 클래스 VipCard(StampCard)에서 메서드를 찾을 때 객체 → 자식 클래스 → 부모 클래스 차례로 찾는 과정을 따라갑니다.

1부모 클래스 물려받기와 메서드 덮어쓰기

클래스 이름 뒤 괄호 안에 부모 클래스 이름을 적어 class VipCard(StampCard):⁠라고 쓰면, VipCard는 StampCard의 모든 메서드를 물려받습니다.

class StampCard:
    def __init__(self, owner):
        self.owner = owner
        self.stamps = 0

    def add(self):
        self.stamps += 1


class VipCard(StampCard):
    def add(self):
        self.stamps += 2
vip = VipCard('민지')
vip.add()
print(vip.owner, vip.stamps)
실행 결과
민지 2

파이썬이 vip 객체에서 메서드를 찾을 때는 늘 객체 → 자식 클래스(VipCard) → 부모 클래스(StampCard) 차례로 올라가며 처음 찾은 것을 실행합니다.

  • VipCard('민지'): VipCard 안에 __init__이 없으므로 부모 StampCard로 올라가 __init__을 실행해 .owner와 .stamps를 채웁니다.
  • vip.add(): VipCard 안에 add가 있으므로 부모까지 올라가지 않고 VipCard의 add에서 멈춰 스탬프를 2 올립니다. 이처럼 부모의 메서드를 자식 클래스에서 같은 이름으로 다시 정의하는 것을 **메서드 오버라이딩(덮어쓰기)**이라고 합니다.

그렇다면 같은 코드에서 일반 StampCard 객체와 VipCard 객체의 .add()를 각각 한 번씩 부르면 어떻게 될까요? 먼저 맞혀 보세요.

c = StampCard('준호')
c.add()
print(c.owner, c.stamps)
실행 결과 보기
실행 결과
민지 2
준호 1

예상과 다르면 그곳이 배울 곳입니다. 앞선 vip의 민지 2에 이어 c는 준호 1이 찍힙니다. 자식 클래스 VipCard에서 add를 덮어썼더라도 부모 클래스 StampCard 자체는 바뀌지 않으므로 c.add()는 StampCard.add를 실행해 1만 오릅니다.

2super()로 부모의 메서드 부르기

자식 클래스에서 __init__을 새로 정의해 속성을 더 추가하고 싶을 때는, 부모가 하던 초기화 일을 먼저 해 주도록 **super().__init__(...)**을 부릅니다.

class StampCard:
    def __init__(self, owner):
        self.owner = owner
        self.stamps = 0


class GiftCard(StampCard):
    def __init__(self, name, n):
        super().__init__(name)
        self.stamps += n


g = GiftCard('민지', 3)
print(g.owner, g.stamps)
실행 결과
민지 3
  • 9번 줄 super().__init__(name): 부모 클래스 StampCard의 __init__을 먼저 불러 self.owner = '민지'와 self.stamps = 0을 채웁니다.
  • 10번 줄 self.stamps += n: 그 위에 가입 보너스 스탬프 3개를 더합니다.

자주 틀리는 곳

1. 자식 __init__에서 super().__init__()을 빠뜨린다

틀린 생각: 자식 클래스에 def __init__(self, ...):⁠을 새로 적더라도 부모의 __init__이 알아서 먼저 실행된다.

자식 클래스에 __init__을 정의하면 부모의 __init__을 덮어쓰므로, 직접 super().__init__(...)⁠을 부르지 않으면 부모의 __init__은 실행되지 않습니다.

class StampCard:
    def __init__(self, owner):
        self.owner = owner
        self.stamps = 0


class VipCard(StampCard):
    def __init__(self, n, lv):
        self.grade = lv


vip = VipCard('민지', '골드')
print(vip.stamps)
실행 결과
Traceback (most recent call last):  File "main.py", line 13, in <module>    print(vip.stamps)          ^^^^^^^^^^AttributeError: 'VipCard' object has no attribute 'stamps'

VipCard.__init__만 실행되고 부모 StampCard.__init__은 건너뛰었기 때문에 .owner와 .stamps 속성이 아예 만들어지지 않았습니다.

맞는 규칙: 자식 클래스에서 __init__을 새로 만들 때는 첫 줄에 super().__init__(...)⁠을 불러 부모가 만드는 속성들을 먼저 초기화합니다.

2. super().__init__에 self까지 다시 넘긴다

틀린 생각: 부모 클래스의 def __init__(self, owner):⁠에 매개변수가 두 개이니 super().__init__(self, owner)⁠처럼 self도 직접 넘겨야 한다.

super().__init__(owner)⁠는 이미 지금 객체(self)를 자동으로 넘겨 줍니다. self를 또 적으면 인자가 하나 더 넘어간 셈이 되어 TypeError가 납니다.

맞는 규칙: super().__init__(...)⁠을 부를 때는 self를 빼고 나머지 인자만 넘깁니다.

확인 문제

1. 다음 코드를 실행하면 무엇이 출력될까요?

class Animal:
    def speak(self):
        return '소리'

class Dog(Animal):
    def speak(self):
        return '멍멍'

class Cat(Animal):
    pass

d = Dog()
c = Cat()
print(d.speak(), c.speak())
힌트

Dog에는 speak가 다시 정의되어 있고, Cat에는 speak가 없어 부모 Animal에서 찾습니다.

정답
실행 결과
멍멍 소리

d.speak()는 Dog에서 덮어쓴 '멍멍'을 돌려주고, c.speak()는 부모 Animal에서 물려받은 '소리'를 돌려줍니다.

2. Account 클래스를 상속받는 SavingAccount 클래스를 정의하려고 합니다. 빈칸을 채우세요.

class Account:
    def __init__(self, owner):
        self.owner = owner


class SavingAccount(________):
    pass


s = SavingAccount('민지')
print(s.owner)
힌트

자식 클래스 이름 뒤의 괄호 안에 물려받을 부모 클래스 이름을 적습니다.

정답

Account입니다 (class SavingAccount(Account):).

3. 아래 코드에서 b = EBook('파이썬', 5)을 만든 뒤 print(b.title)을 실행하면 AttributeError가 납니다. EBook의 __init__ 안에 어떤 한 줄을 추가해야 할까요?

class Book:
    def __init__(self, t):
        self.title = t


class EBook(Book):
    def __init__(self, t, n):
        self.size = n
힌트

부모 클래스 Book의 __init__을 불러 self.title이 채워지도록 super()를 써 보세요.

정답

EBook의 __init__ 안에 super().__init__(t) 한 줄을 추가합니다.

class Book:
    def __init__(self, t):
        self.title = t


class EBook(Book):
    def __init__(self, t, n):
        super().__init__(t)
        self.size = n


b = EBook('파이썬', 5)
print(b.title, b.size)
실행 결과
파이썬 5

아래 코드에서 VipCard의 add가 스탬프를 3개씩 올리도록 바꾸어 돌려 보세요.

처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

파이썬 목차로