Menu

파이썬 상속: 하위 클래스, super(), 오버라이딩, MRO

파이썬에서 클래스 상속이 동작하는 방식: 하위 클래스 만들기, super().__init__ 호출, 메서드 오버라이딩과 확장, isinstance와 issubclass, 다중 상속과 MRO, 그리고 합성이 더 나은 경우.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

다른 클래스 위에 세운 클래스

상속을 쓰면 기존 클래스를 확장한 새 클래스를 정의할 수 있어요. 기존 클래스는 부모(기반 클래스, 상위 클래스라고도 해요)이고, 새 클래스는 자식(하위 클래스)입니다. 자식은 부모의 모든 메서드와 클래스 속성을 다시 쓰지 않고도 물려받고, 자기만의 것을 추가하거나 다르게 동작해야 하는 것을 교체할 수 있어요.

부모는 클래스 이름 뒤 괄호 안에 선언해요:

출력:

Rex is an animal
Woof

Dog에는 자체 __init__도 describe도 없어요. Dog("Rex")를 호출하면 파이썬은 Dog에서 __init__을 찾고, 없으니 Animal의 것을 씁니다. rex.describe()도 같은 방식으로 찾아요. rex.speak()의 경우에는 Dog에서 speak를 먼저 찾고 거기서 멈춰요.

이 탐색 순서가 메커니즘의 전부예요. 파이썬은 객체 자신의 클래스, 그다음 부모, 그다음 부모의 부모를 차례로 찾아서 처음 일치하는 것을 씁니다. 클래스가 처음이라면 클래스 페이지에서 __init__, self, 메서드를 먼저 보세요.

메서드 오버라이딩

자식에 부모와 같은 이름의 메서드가 있으면, 자식의 인스턴스에서는 그 메서드가 부모의 것을 대신해요. 이를 오버라이딩이라고 합니다. 하위 클래스마다 같은 호출에 자기 방식으로 답할 수 있어요:

출력:

Rectangle with area 12
Square with area 25
Shape with area 0

summary는 Shape에 한 번만 정의되어 있지만 self.area()를 호출해요. self가 실제 객체이기 때문에 Rectangle은 Rectangle.area를 실행하고, Square는 부모인 Rectangle에서 area를 찾아요. 부모의 코드가 자식의 존재를 모르면서도 자식의 메서드를 호출하는 거예요. 공통된 작업 흐름은 함께 쓰고 세부 사항은 하위 클래스마다 채우게 할 수 있다는 점이 상속을 유용하게 만들어요.

super()로 부모 호출하기

자식이 자기 __init__을 정의하면 부모의 __init__은 더 이상 저절로 실행되지 않아요. 자식에게도 부모의 초기화가 필요하다면 super().__init__()으로 호출하세요:

출력:

Ada 1050.0

super()는 현재 클래스의 부모부터 메서드를 찾는 객체를 돌려줘요. 그래서 super().__init__(owner, balance)는 같은 객체에서 Account.__init__을 실행하고, 그 뒤에는 self.owner와 self.balance가 존재해요.

그 줄을 빼먹으면 객체는 반쯤만 만들어져요:

출력:

0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'

대신 Account.__init__(self, owner)라고 쓸 수도 있고, 오래된 코드에서는 흔히 그렇게 써요. super()가 권장되는 이유는 부모 이름을 반복하지 않고, 아래에서 다룰 다중 상속과도 올바르게 협력하기 때문이에요.

메서드를 교체하지 않고 확장하기

super()는 __init__뿐 아니라 어떤 메서드에서든 동작해요. 오버라이드한 메서드 안에서 부모의 버전을 호출하면, 다시 쓰는 대신 그 주위에 동작을 덧붙일 수 있어요:

출력:

started
[12:00] started
INFO [12:00] started

각 단계가 조각 하나를 추가하고 나머지는 위로 넘겨요. 나중에 부모의 format이 바뀌면 두 자식 모두 그 변경을 그대로 반영해요.

isinstance와 issubclass

하위 클래스의 인스턴스는 그 위에 있는 모든 클래스의 인스턴스이기도 해요. isinstance(obj, cls)는 그 관계를 검사하고, issubclass(child, parent)는 두 클래스 사이의 관계를 검사합니다:

type(obj)를 직접 비교하는 것보다 isinstance를 쓰세요. type(obj) is Animal 같은 검사는 모든 하위 클래스를 거부하기 때문에, 누군가 Dog를 추가하는 순간 깨져요.

직접 작성하는 모든 클래스는 따로 적지 않아도 object를 상속해요. class Animal:은 class Animal(object):와 같아요. __str__, __repr__, __eq__ 같은 기본 메서드가 여기서 오고, 그래서 __str__을 오버라이드하면 print()가 보여주는 내용이 바뀌어요:

출력:

(2, 3)
True

하위 클래스에 메서드 구현 강제하기

부모 클래스를 직접 쓰지 않고 틀로만 써야 할 때가 있어요. abc 모듈은 이런 클래스를 추상 클래스로 표시해요. @abstractmethod가 붙은 메서드는 모두 오버라이드해야 그 클래스의 인스턴스를 만들 수 있어요:

출력의 처음 세 줄은 내보낸 결과예요:

exporting 2 rows
1,2
3,4

마지막 줄은 Exporter와 빠진 export 메서드의 이름을 알려주는 TypeError예요(정확한 문구는 파이썬 버전마다 달라요). 실수가 나중에 export가 드디어 호출될 때가 아니라 객체를 만드는 시점에 드러나요.

다중 상속과 MRO

클래스는 부모를 여러 개 나열할 수 있어요. 그러면 모든 부모를 상속합니다:

출력:

paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']

두 부모 모두 move를 정의하고, Duck은 Swimmer의 것을 받아요. 파이썬은 메서드 결정 순서(MRO)로 이를 정해요. MRO는 Duck.__mro__에 저장된 클래스 목록 하나이고, 모든 속성 탐색이 이 목록을 앞에서 뒤로 훑어요. 클래스 자신으로 시작해서, 부모들은 나열한 순서를 지키고, 모든 클래스를 그 부모보다 앞에 두며, object로 끝나요. 순서를 바꿔 class Duck(Flyer, Swimmer)로 쓰면 move는 "flies"를 돌려줘요.

super()도 같은 목록을 따라가요. Swimmer의 메서드 안에서 super()는 "객체의 클래스의 MRO에서 Swimmer 다음에 오는 클래스"를 뜻하고, Duck에서는 그게 object가 아니라 Flyer예요. 그래서 협력하는 클래스들이 각자 super()를 호출하면, 두 부모가 같은 조부모를 공유하는 다이아몬드 구조에서도 사슬 안의 모든 클래스가 정확히 한 번씩 실행돼요.

실무에서 다중 상속은 주로 믹스인에 쓰여요. 믹스인은 to_json 메서드를 추가하는 JsonMixin처럼 기능 하나를 더하는 작은 클래스로, 주된 부모 하나와 함께 조합해요. 완전한 부모를 여럿 둔 깊은 계층 구조는 따라가기 어렵고 필요한 경우도 드물어요.

상속일까, 합성일까?

상속은 "is a" 관계를 모델링해요. SavingsAccount는 Account예요. Account를 다루는 코드라면 무엇이든 수정 없이 SavingsAccount로도 동작해야 해요. 이 문장이 어색하게 들린다면 아마 합성이 필요한 경우일 거예요. 합성은 "has a" 관계를 모델링해요. 객체가 다른 객체를 속성으로 갖고 그 객체를 호출하는 방식이에요.

출력:

Roadster: 300 hp engine running

class Car(Engine)도 실행은 되지만, 자동차가 엔진의 한 종류라고 주장하는 셈이 되고, Car에 엔진의 모든 메서드가 생기며, 클래스를 새로 만들지 않고는 다른 엔진으로 바꿀 수 없게 돼요. 합성을 쓰면 부품이 분리된 채로 남고, 엔진을 바꾸려면 다른 객체를 넘기기만 하면 돼요.

상속이 맞지 않는 도구라는 신호 몇 가지:

  • 자식이 부모 메서드 대부분을 오버라이드하거나, 일부를 쓸 수 없게 막아요.
  • 헬퍼 메서드 한두 개를 재사용하려고만 상속해요.
  • 계층이 두세 단계를 넘어가서, 메서드 하나가 무엇을 하는지 보려고 파일을 여러 개 읽어야 해요.

흔한 실수

  • super().__init__()을 잊기. 자식의 __init__이 부모의 것을 대체하니까 부모의 속성이 설정되지 않고, 나중에 AttributeError가 나요.
  • super() 호출에 self를 넘기기. super().__init__(self, name)이 아니라 super().__init__(name)이라고 쓰세요. super()가 이미 메서드를 현재 객체에 묶어 줘요.
  • 오버라이드할 때 시그니처가 맞지 않음. 공통 코드가 부모의 speak(self)를 호출한다면, 필수 인자가 하나 더 있는 자식의 speak(self, volume)은 그 코드를 깨뜨려요. 오버라이드는 호환되게 유지하거나, 새 매개변수에 기본값을 주세요.
  • type(obj) == SomeClass로 검사하기. 하위 클래스를 무시해요. isinstance를 쓰세요.
  • 관련 없는 클래스끼리 코드를 나누려고 상속 쓰기. 공유할 코드는 함수나 헬퍼 객체로 옮기세요.

자주 묻는 질문

파이썬에서 클래스를 어떻게 상속하나요?

새 클래스 이름 뒤 괄호 안에 부모 클래스를 적으세요: class Dog(Animal):. Dog는 Animal의 모든 메서드와 클래스 속성을 물려받고, 새 메서드를 추가하거나 기존 메서드를 오버라이드할 수 있어요.

super().__init__()은 무엇을 하나요?

부모 클래스의 __init__을 호출해서 부모가 자기 속성을 설정하게 해요. 하위 클래스가 자체 __init__을 정의하면 파이썬은 부모의 __init__을 자동으로 호출하지 않아요. 그래서 부모에게 필요한 인자로 super().__init__(...)을 호출한 다음, 하위 클래스에만 있는 속성을 설정합니다.

파이썬에서 isinstance와 type은 뭐가 다른가요?

isinstance(obj, Animal)은 Animal의 인스턴스뿐 아니라 Animal의 어떤 하위 클래스의 인스턴스에도 True예요. type(obj) is Animal은 객체가 Animal 자체로 만들어졌을 때만 True입니다. 누군가 하위 클래스를 추가해도 계속 동작하니 isinstance를 쓰세요.

파이썬은 다중 상속을 지원하나요?

네. class C(A, B):는 두 클래스를 모두 상속해요. 두 부모가 같은 메서드를 정의하면 파이썬은 메서드 결정 순서(MRO)를 따라 하나를 고르고, 그 순서는 C.__mro__로 확인할 수 있어요. 맨 앞에 C, 그다음 부모들이 왼쪽에서 오른쪽 순서로, 맨 끝에 object가 옵니다.

언제 상속 대신 합성을 써야 하나요?

상속은 "is a" 관계, 즉 부모가 쓰이는 모든 자리에 하위 클래스를 쓸 수 있을 때 쓰세요(SavingsAccount는 Account예요). 객체를 속성으로 저장하는 합성은 "has a" 관계에 쓰세요(Car는 Engine을 가지고 있어요). 확신이 없으면 합성이 나중에 바꾸기 더 쉬워요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기