파이썬과 상속
이 글의 작성자는 C/C++ 프로그래밍을 하던 사람이다. 이 글은 Python을 복습하며 작성하는 글이니, 부족한 부분이 있으면 얼마든지 피드백을 주시기 바란다.
person 클래스로 학생 표현하기
사람도 다양한 형태의 사람이 존재한다. 공부하는 사람, 운동하는 사람, 일하는 사람… 표현하고자 한다면 정말 다양한 형태로 사람을 표현할 수 있을 것이다.
그러면 person
클래스로 공부하는 사람 중에서 대학생을 표현해보자. 대학생은 어떤 속성을 가지고 있을까?
- 다니는 학교
- 학년
- 전공
아마 위의 3가지 보다 더 많은 속성을 가지고 있겠지만, 지금은 이 3가지만 가지고 대학생을 표현해보자.
그러면 기존 person
클래스에 위의 3가지 속성을 객체 속성으로 추가하면 될 것이다. 다니는 학교, 학년, 전공은 개개인별로 다른 속성을 지닐 수 있기 때문이다.
그러면 코드로 표현해보자. 지난 포스트에서 추가했던 클래스 메서드와 스태틱 메서드는 일단 지워두자.
class person:
def __init__(self, _name, _age, _university, _grade, _major):
self.name = _name
self.age = _age
self.university = _university
self.grade = _grade
self.major = _major
def introduce(self):
print("안녕하세요! 저는", self.name, "입니다!", self.age, "살 이에요!")
print(self.university,'대학교',self.grade,'학년이고,',self.major,'전공이에요!')
p = person('mike', 23, '서울',2,'컴퓨터')
p.introduce()
위의 코드를 실행하면 다음과 같은 결과가 나올 것이다.
안녕하세요! 저는 mike 입니다! 23 살 이에요!
서울 대학교 2 학년이고, 컴퓨터 전공이에요!
얼추 대학생을 표현하는 클래스가 되었다.
하지만 생각해보자. 세상 모든 사람이 대학생인가? 아니다. 대학생인 사람도 있지만, 대학생이 아닌 사람도 있다. 만약 대학생이 아닌 사람을 표현하기 위해 객체를 생성하려 한다고 치자. 그렇다면 위의 속성 중에서 대학교, 학년, 전공은 채울 수 없는 속성이 된다. 왜냐하면 대학생이 아니니까.
학생은 사람이지만, 사람이라고 학생은 아니다. 이를 어떻게 하면 좋을까?
하나의 방법으로는 person
클래스를 복사해서, student
클래스를 따로 만드는 것이다.
class student:
def __init__(self, _name, _age, _university, _grade, _major):
self.name = _name
self.age = _age
self.university = _university
self.grade = _grade
self.major = _major
def introduce(self):
print("안녕하세요! 저는", self.name, "입니다!", self.age, "살 이에요!")
print(self.university,'대학교',self.grade,'학년이고,',self.major,'전공이에요!')
그리고 원본 person
클래스를 남겨놓으면 해결될 것 같다.
하지만 이러면 무엇이 문제가 되느냐? 관리해야 하는 코드의 양이 늘어난다. 만약 사람의 기본 속성이 추가되어야 할 일이 생겼다고 치자. (예를 들어 성별을 구분해야 할 일이 생겼다거나) 그러면 person
클래스 코드를 기반으로 작성한 다른 모든 클래스들도 영향이 갈 수가 있다.
지금이야 student
클래스 하나만 복사했지만, 시간이 지나면서 singer
클래스, worker
클래스, teacher
클래스 등 클래스들이 점점 많이 생겨나면 어떻게 되겠는가? 일괄적으로 수정해야 할 요소를 깜박하고 수정하지 못하는 경우, 즉 사람의 실수가 발생할 가능성이 생긴다.
상속(inheritance)
그래서 파이썬은 상속(inheritance)라는 것을 지원한다. 상속의 사전적인 뜻은 무엇인가? 대충 무언가를 이어받는다라는 것을 의미한다. 부모로부터 재산을 물려받는다던지. 그러면 파이썬에서의 상속은 무엇일까?
원본 클래스를 바탕으로 새로운 클래스를 설계할 수 있도록 도와주는 기능을 상속이라고 한다. 상속을 사용하면 원본 클래스의 내용을 이어받은 상태에서 클래스에 추가적인 속성을 추가하거나, 메서드를 추가/수정할 수 있다.
그러면 상속은 어떻게 하는건지 알아보자.
일단은 원본 person
클래스를 상속만 한 sutdent
클래스를 만들어보자.
class student(person):
pass
person
클래스를 정의했을 때와 다른 것은 무엇인가? 마치 함수를 호출하는 것 처럼 괄호가 있다.
그렇다. 상속받고자 하는 클래스를 괄호 안에 집어넣기만 하면 끝난다. 그러면 student
클래스는 person
클래스의 코드를 사용하는 또 하나의 클래스가 된 것이다. 지금은 아무런 코드를 적지 않고 pass
만 입력했기 때문에, person
클래스와 완전히 동일한 동작을 수행한다.
실제로 student
클래스가 person
클래스 역할을 그대로 수행하는지 확인해보자.
p = person('mike', 23)
p.introduce()
s = student('mike', 23)
s.introduce()
위의 코드를 실행하면 person
클래스로 생성한 객체와 student
클래스로 생성한 객체 둘 다 같은 내용을 출력하고 있는 것을 확인할 수 있을 것이다.
안녕하세요! 저는 mike 입니다! 23 살 이에요!
안녕하세요! 저는 mike 입니다! 23 살 이에요!
클래스 간의 관계
그러면 두 클래스 간의 관계에 대해 알아야 할 것들을 살펴보자.
관계의 명칭
person
클래스는 다음과 같은 이름으로 불린다.
- 부모(parent) 클래스
- 슈퍼(super) 클래스
- 베이스(base) 클래스
그리고 person
클래스를 기반으로 만들어진 student
클래스는 다음과 같은 이름으로 불린다.
- 자식(child) 클래스
- 서브(sub) 클래스
- 파생(derived) 클래스
이 포스트에서는 부모 클래스와 자식 클래스라는 명칭을 사용하도록 한다.
is - a 관계
부모 클래스와 자식 클래스는 is - a 관계라고 부른다. 이게 무슨 말인가라는 생각이 들 수 있다. 혹시 이 글을 읽다가 다음과 같이 쓴 문구를 기억하는가?
“학생은 사람이지만, 사람이라고 학생은 아니다. 이를 어떻게 하면 좋을까?”
“학생은 사람”. 이 말을 영어로 하면 어떻게 표현할 수 있는가?
student
is a person
이라고 표현할 수 있다.
즉, 자식 클래스
is a 부모 클래스
로 표현할 수 있다는 것이다. 자식 클래스
는 부모 클래스
의 코드를 기반으로 만들어졌기 때문에 어찌보면 당연한 이야기이다.
그러면 사람을 표현하는 클래스인 person 클래스로부터 자동차를 표현하는 클래스인 car 클래스를 상속받아 생성하면 car is a person이냐? 라는 의문이 들 수 있다. 파이썬 문법 상으로는 문제가 없겠지만, 의미 상으로는 크게 문제가 있으므로, 애초에 그렇게 작성하지 말아야 한다. 애초에 상속을 잘못 사용한 것이 된다.
student 클래스 다듬기
그러면 person
클래스로부터 상속 받은 student
클래스를 본격적으로 작성해보자. 가장 먼저 이니셜라이저를 손 봐야 할 것 같다. person
클래스와 비교해서 새로 추가되는 속성이 3가지나 있기 때문이다.
그러면 한 번 작성해보자. 우선은 다음과 같이 작성해보자.
class student(person):
def __init__(self, _name, _age, _university, _grade, _major):
self.name = _name
self.age = _age
self.university = _university
self.grade = _grade
self.major = _major
def introduce(self):
print("안녕하세요! 저는", self.name, "입니다!", self.age, "살 이에요!")
print(self.university,'대학교',self.grade,'학년이고,',self.major,'전공이에요!')
위와 같이 작성하면 잘 돌아가는 것 같긴 한데, 뭔가 이상하다. 상속을 받지 않고 작성한 코드와 다를 바가 없다. 상속한 보람이 없는 코드이다.
그러면 어떻게 하는 것이 이상적일까? 부모 클래스와 공통으로 사용하는 부분은 부모 클래스의 코드를 사용하는 것이 이상적일 것이다. 그러면 부모 클래스의 코드를 사용하려면 어떻게 해야 하는가?
super() 메서드를 사용하면 된다. 이 메서드는 부모 클래스의 형태로 된 객체를 돌려준다. introduce
메서드에서 print(super())
코드를 실행하면 다음과 같은 결과가 나올 것이다.
<super: <class 'student'>, <student object>>
잘 모르겠으면, 부모 클래스의 메서드를 쓰고 싶다면 앞에 super()를 붙인다 라는 사실만 기억하면 된다.
그러면 super()를 사용해서 student
클래스를 다듬어보자.
class student(person):
def __init__(self, _name, _age, _university, _grade, _major):
super().__init__(_name, _age)
self.university = _university
self.grade = _grade
self.major = _major
def introduce(self):
super().introduce()
print(self.university,'대학교',self.grade,'학년이고,',self.major,'전공이에요!')
이러면 person
클래스에서 이니셜라이저나 introduce
메서드가 바뀐다 하더라도 student
클래스에서 크게 신경 쓸 것은 없어진다.
person 클래스에서 새로운 속성을 하나 받아야 한다면 student 클래스도 person 클래스에서 추가된 새로운 속성을 받도록 수정해야 하긴 한다.
메서드 오버라이드(override)
잘 생각해보면 person
클래스와 student
클래스는 같은 이름의 메서드, introducce
를 사용하고 있다. 그런데 이름 충돌 같은건 일어나지 않고, student
객체는 student
클래스에서 정의된 introduce
를 호출하고 있다.
위와 같이 부모 클래스가 가지고 있는 메서드를 재정의하는 경우, 메서드를 오버라이드(override) 한다라고 이야기한다. 상속에 대해서 소개할 때, ‘메서드를 추가/수정할 수 있다’라는 이야기가 나왔는데, 메서드 오버라이드가 메서드를 수정하는 동작이라고 볼 수 있다.
student 클래스만의 메서드 추가하기
그러면 student
클래스, 즉 학생만이 수행할 수 있는 동작을 하나 만들어보자.
학생의 본분은 무엇인가? 공부이다. 따라서 study()
라는 메서드를 하나 만들면 될 것 같다. 이 메서드를 호출하면 “전공 과목을 공부한다” 라는 식의 메시지를 출력하면 될 것 같다.
class student(person):
def __init__(self, _name, _age, _university, _grade, _major):
super().__init__(_name, _age)
self.university = _university
self.grade = _grade
self.major = _major
def introduce(self):
super().introduce()
print(self.university,'대학교',self.grade,'학년이고,',self.major,'전공이에요!')
def study(self):
print(self.major, "공부를 합니다!")
그러면 이제 student
객체는 study
메서드를 호출할 수 있게 되었다.
그러면 person
객체는 study
를 호출할 수 있는가?
아니다. person
은 student
가 아니기 때문에 study
메서드를 호출할 수 없다. person
객체로 study
를 호출하려고 하면 해당하는 메서드를 찾을 수 없다는 오류가 발생할 것이다.
즉, 상속받은 클래스는 원본 클래스가 하지 못하는 특별한 무엇가를 할 수 있으며, 이것이 상속받은 클래스의 개성을 나타낸다.
Comments