2. 상속#
슬라이드
본문 내용을 요약한 슬라이드를 다운로드할 수 있다.
2.1. 상속이란?#
클래스를 선언할 때 다른 클래스의 속성과 메서드를 상속inheritance해서 활용할 수 있다. 상속을 받는 클래스를 자식 클래스 또는 하위 클래스, 상속을 하는 클래스를 부모 클래스 또는 상위 클래스라고 부른다.
상속을 이용하여 클래스를 정의하는 방식은 다음과 같다.
class 자식클래스(부모클래스):
클래스 본문
2.2. 모음 자료형의 상속 체계#
아래 그림은 파이썬 모음 자료형의 상속 체계를 보여준다.
예를 들어, list
클래스는 Sequence
클래스를 상속하며,
Sequence
는 Collection
클래를 상속한다.
이렁 이유로 “리스트는 순차Sequence 자료형이다” 등으로 말한다.
이와 달리 항목들의 순서를 고려하지 않는 dict
와 set
은 순차 자료형이 아니다.

<그림 출처: Problem Solving with Algorithms and Data Structures using Python의 1.13 절>
list
, tuple
, str
클래스 모두 Sequence
클래스를 상속하기에
인덱싱, 슬라이싱 등 자신들의 항목을 다루는 공통된 방식을 갖는다.
반면에 각 자료형은 고유의 메서드도 제공한다.
이렇듯 한 클래스의 여러 자식 클래스는 서로 공통된 요소와 함께
각 자식 클래스 고유의 요소를 갖는다.
클래스를 상속할 때의 가장 큰 장점은 다음과 같다.
첫째, 기존에 작성된 코드를 필요에 따라 수정하고 재활용 할 수 있다.
둘째, 자식 클래스의 인스턴스들 사이의 관계를 보다 잘 이해할 수 있다.
여기서는 Vector
클래스를 선언할 때 상속을 어떻게 활용하는지 보여준다.
2.3. Vector 클래스#
벡터는 정수 또는 부동소수점으로 이루어진 리스트와 유사한 모음 자료형이다. Numpy의 1차원 어레이array가 대표적인 벡터 자료형이다. 벡터는 벡터 자체의 길이, 내적 등 리스트와는 다른 속성과 기능을 제공한다.
벡터 내적
길이(항목의 개수)가 동일한 두 벡터의 내적은 동일한 위치의 두 항목의 곱셈의 합이다.
예를 들어 [2, 3, 4]
와 [5, 6, 9]
두 벡터의 내적은 다음과 같다.
2 * 5 + 3 * 6 + 4 * 9
Vector
클래스 선언
아래 코드는 list
클래스를 상속하면서 벡터 내적 연산을 지원하는 Vector
클래스를
정의한다.
super().__init__()
: 부모 클래스의 생성자 호출. 자식 클래스의 생성자를 호출할 때 호출되면 부모 클래스의 속성과 메서드를 모두 상속받음.dot()
메서드: 추가되는 메서드. 두 벡터의 내적 반환.len
속성: 추가되는 인스턴스 속성. 벡터의 길이.
class Vector(list):
# Vector 클래스 생성자 재정의
def __init__(self, items):
"""
- list 클래스 상속
- items: 벡터로 사용될 리스트
"""
# 부모 클래스 생성자 호출
super().__init__(items)
# 속성 추가
self.len = self.__len__()
# 내적 메서드
def dot(self, other):
"""
벡터 내적
"""
# 벡터의 길이가 다르면 실행 오류 발생
if self.len != other.len:
raise RuntimeError("두 벡터의 길이가 달라요!")
# 내적 계산: 각 항목들의 곱의 합
# 리스트를 상속하기에 인덱싱 사용 가능
sum = 0
for i in range(self.len):
sum += self[i] * other[i]
return sum
벡터 객체 활용
두 개의 벡터를 생성하자.
x = Vector([2, 3, 4])
y = Vector([5, 6, 9])
__str__()
등 리스트의 모든 매직 메서드와
append()
등의 다른 모든 메서드,
그리고 인덱싱, 슬라이싱 등
리스트의 모든 기능을 활용할 수 있다.
__str__()
메서드 지원
print(x)
[2, 3, 4]
내적: 내적 연산도 잘 작동함
x.dot(y) # 2*5 + 3*6 + 4*9
64
2.4. 메서드 재정의#
부모 클래스로부터 상속 받은 인스턴스 메서드의 일부를 재정의해야 하는 경우가 발생할 수 있다.
2.4.1. append()
, pop()
메서드 재정의#
append()
메서드도 잘 작동한다.
x.append(5)
x
[2, 3, 4, 5]
그런데 벡터에 포함된 항목의 수, 즉 len
속성의 값이 4로 변하지 않는다.
x.len
3
반면에 리스트의 __len__()
메서드는 정상적으로 작동한다.
x.__len__()
4
__len__()
메서드를 활용하는 len()
함수도 동일하게 잘 작동한다.
len(x)
4
len
속성이 변경된 항목의 개수를 제대로 반영하지 못하는
이유는 벡터의 항목이 변할 때 항목의 개수를 재확인해주지 않기 때문이다.
이 문제를 해결하려면 append()
, pop()
등 항목의 개수에 영향을 주는 메서드가
실행되면 자동으로 항목의 수를 조정하도록 해야 한다.
여기서는 예시를 위해 상속받은 append()
와 pop()
두 메서드를 재정의 한다.
class Vector(list):
# Vector 클래스 생성자 재정의
def __init__(self, items):
"""
- list 클래스 상속
- items: 벡터로 사용될 리스트
"""
# 부모 클래스 생성자 호출
super().__init__(items)
# 속성 추가
self.len = self.__len__()
# 내적 메서드
def dot(self, other):
"""
벡터 내적
"""
# 벡터의 길이가 다르면 실행 오류 발생
if self.len != other.len:
raise RuntimeError("두 벡터의 길이가 달라요!")
# 내적 계산: 각 항목들의 곱의 합
# 리스트를 상속하기에 인덱싱 사용 가능
sum = 0
for i in range(self.len):
sum += self[i] * other[i]
return sum
# append() 메서드 재정의
def append(self, item):
super().append(item) # 부모 클래스의 append() 메서드 호출
self.len = self.__len__() # 항목 수 다시 확인
# pop() 메서드 재정의
def pop(self, idx=-1):
popped = super().pop(idx) # 부모 클래스의 pop() 메서드 호출
self.len = self.__len__() # 항목 수 다시 확인
return popped
다시 두 개의 벡터를 생성하자.
x = Vector([2, 3, 4])
y = Vector([5, 6, 9])
append()
메서드 (다시)
x.append(5)
x
[2, 3, 4, 5]
이제 벡터의 길이가 달라진게 확인된다.
x.len
4
x.__len__()
4
len(x)
4
pop()
메서드
x.pop()
5
x
[2, 3, 4]
x.pop(1)
3
x
[2, 4]
벡터의 길이도 달라진다.
x.len
2
x.__len__()
2
len(x)
2
인덱싱/슬라이싱
인덱싱, 슬라이싱 등 재정의 되지 않은 리스트의 다른 기능은 동일하게 작동한다.
인덱싱
x[0]
2
슬라이싱
x[:2]
[2, 4]
외부 함수 선언: 메서드 활용
len(x)
을 실행하면 실제로는 x.__len__()
이 실행된다.
이처럼 자주 활용되는 메서드를 일반 함수로 선언해서 활용하면 편리할 수 있다.
예를 들어, 벡터의 내적을 자주 계산하는 경우가 그렇다.
아래 dot()
함수는 벡터 인자에 대해서만 작동하도록 구현되었다.
def dot(x, y):
assert isinstance(x, Vector) and isinstance(y, Vector)
return x.dot(y)
x.append(7)
x
[2, 4, 7]
dot(x, y)
97
정의된 대로 실제로는 Vector
클래스의 dot()
메서드가 실행된다.
x.dot(y) == dot(x, y)
True
2.4.2. __add__()
매직 메서드 재정의: 벡터 합#
넘파이 어레이의 경우처럼
길이가 동일한 두 벡터의 합(+
)을 항목별 합으로 정의하려 한다.
그러려면 __add__()
매직 메서드를 재정의해야 한다.
class Vector(list):
# Vector 클래스 생성자 재정의
def __init__(self, items):
"""
- list 클래스 상속
- items: 벡터로 사용될 리스트
"""
# 부모 클래스 생성자 호출
super().__init__(items)
# 속성 추가
self.len = self.__len__()
# 벡터 합 메서드 재정의
def __add__(self, other):
"""
벡터 합
"""
# 벡터의 길이가 다르면 실행 오류 발생
if self.len != other.len:
raise RuntimeError("두 벡터의 길이가 달라요!")
# 벡터 합 계산: 각 항목들의 합으로 이루어진 벡터
new_list = []
for i in range(self.len):
item = self[i] + other[i]
new_list.append(item)
return Vector(new_list)
# append() 메서드 재정의
def append(self, item):
super().append(item) # 부모 클래스의 append() 메서드 호출
self.len = self.__len__() # 항목 수 다시 확인
# pop() 메서드 재정의
def pop(self, idx=-1):
popped = super().pop(idx) # 부모 클래스의 pop() 메서드 호출
self.len = self.__len__() # 항목 수 다시 확인
return popped
# 내적 메서드
def dot(self, other):
"""
벡터 내적
"""
# 벡터의 길이가 다르면 실행 오류 발생
if self.len != other.len:
raise RuntimeError("두 벡터의 길이가 달라요!")
# 내적 계산: 각 항목들의 곱의 합
# 리스트를 상속하기에 인덱싱 사용 가능
sum = 0
for i in range(self.len):
sum += self[i] * other[i]
return sum
# dot 함수도 새로 정의해야 함.
def dot(x, y):
assert isinstance(x, Vector) and isinstance(y, Vector)
return x.dot(y)
클래스를 수정하면 인스턴스를 새로 생성해야 변경된 내용이 반영된다.
x = Vector([2, 3, 4])
y = Vector([5, 6, 9])
이제 벡터의 합이 원하는 대로 지원된다.
x + y
[7, 9, 13]
2.5. 연습문제#
참고: (실습) 상속