day 8 사람 클래스로 학생 클래스 만들기

4 minute read

클래스 상속 사용하기

클래스 속성과 인스턴스 속성

상속은 무언가를 물려받는다는 뜻입니다. 그래서 클래스 상속은 물려받은 기능을 유지한채로 다른 기능을 추가할 때 사용하는 기능입니다. 여기서 기능을 물려주는 클래스를 기반 클래스(base class), 상속을 받아 새롭게 만드는 클래스를 파생 클래스(derived class)라고 합니다.

image

보통 기반 클래스는 부모 클래스(parent class), 슈퍼 클래스(superclass)라고 부르고, 파생 클래스는 자식 클래스(child class), 서브 클래스(subclass)라고도 부릅니다.

사람 클래스로 학생 클래스 만들기

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class Student(Person):
    def study(self):
        print('공부하기')
 
james = Student()
james.greeting()    # 안녕하세요.: 기반 클래스 Person의 메서드 호출
james.study()       # 공부하기: 파생 클래스 Student에 추가한 study 메서드
..
..
안녕하세요.
공부하기

참고 | 상속 관계 확인하기

클래스의 상속 관계를 확인하고 싶을 때는 issubclass 함수를 사용합니다. 즉, 클래스가 기반 클래스의 파생 클래스인지 확인합니다. 기반 클래스의 파생 클래스가 맞으면 True, 아니면 False를 반환합니다.

issubclass(파생클래스, 기반클래스)

>>> class Person:
...     pass
...
>>> class Student(Person):
...     pass
...
>>> issubclass(Student, Person)
True

Student가 Person의 파생 클래스이므로 issubclass는 True가 나옵니다.

상속 관계

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class Student(Person):
    def study(self):
        print('공부하기')

여기서 학생 Student는 사람 Person이므로 같은 종류입니다. 이처럼 상속은 명확하게 같은 종류이며 동등한 관계일 때 사용합니다. 즉, “학생은 사람이다.”라고 했을 때 말이 되면 동등한 관계입니다. 그래서 상속 관계를 영어로 is-a 관계라고 부릅니다(Student is a Person).

포함 관계

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class PersonList:
    def __init__(self):
        self.person_list = []    # 리스트 속성에 Person 인스턴스를 넣어서 관리
 
    def append_person(self, person):    # 리스트 속성에 Person 인스턴스를 추가하는 함수
        self.person_list.append(person)

여기서는 상속을 사용하지 않고 속성에 인스턴스를 넣어서 관리하므로 PersonList가 Person을 포함하고 있습니다. 이러면 사람 목록 PersonList와 사람 Person은 동등한 관계가 아니라 포함 관계입니다. 즉, “사람 목록은 사람을 가지고 있다.”라고 말할 수 있습니다. 그래서 포함 관계를 영어로 has-a 관계라고 부릅니다(PersonList has a Person).

super()로 기반 클래스 초기화하기

class Person:
    def __init__(self):
        print('Person __init__')
        self.hello = '안녕하세요.'
 
class Student(Person):
    def __init__(self):
        print('Student __init__')
        super().__init__()                # super()로 기반 클래스의 __init__ 메서드 호출
        self.school = '파이썬 코딩 도장'
 
james = Student()
print(james.school)
print(james.hello)
..
..
Student __init__
Person __init__
파이썬 코딩 도장
안녕하세요.

기반 클래스를 초기화하지 않아도 되는 경우

class Person:
    def __init__(self):
        print('Person __init__')
        self.hello = '안녕하세요.'
 
class Student(Person):
    pass
 
james = Student()
print(james.hello)
..
..
Person __init__
안녕하세요.

이처럼 파생 클래스에 __init__ 메서드가 없다면 기반 클래스의 __init__이 자동으로 호출되므로 기반 클래스의 속성을 사용할 수 있습니다.

참고 | 좀 더 명확하게 super 사용하기

super는 다음과 같이 파생 클래스와 self를 넣어서 현재 클래스가 어떤 클래스인지 명확하게 표시하는 방법도 있습니다. 물론 super()와 기능은 같습니다.

super(파생클래스, self).메서드

class Student(Person):
    def __init__(self):
        print('Student __init__')
        super(Student, self).__init__()     # super(파생클래스, self)로 기반 클래스의 메서드 호출
        self.school = '파이썬 코딩 도장'

메서드 오버라이딩 사용하기

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class Student(Person):
    def greeting(self):
        print('안녕하세요. 저는 파이썬 코딩 도장 학생입니다.')
 
james = Student()
james.greeting()
..
..
안녕하세요. 저는 파이썬 코딩 도장 학생입니다.


class Person:
    def greeting(self):
        print('안녕하세요.')
 
class Student(Person):
    def greeting(self):
        super().greeting()    # 기반 클래스의 메서드 호출하여 중복을 줄임
        print('저는 파이썬 코딩 도장 학생입니다.')
 
james = Student()
james.greeting()
..
..
안녕하세요.
저는 파이썬 코딩 도장 학생입니다.

다중 상속 사용하기

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class University:
    def manage_credit(self):
        print('학점 관리')
 
class Undergraduate(Person, University):
    def study(self):
        print('공부하기')
 
james = Undergraduate()
james.greeting()         # 안녕하세요.: 기반 클래스 Person의 메서드 호출
james.manage_credit()    # 학점 관리: 기반 클래스 University의 메서드 호출
james.study()            # 공부하기: 파생 클래스 Undergraduate에 추가한 study 메서드
..
..
안녕하세요.
학점 관리
공부하기

다이아몬드 상속

class A:
    def greeting(self):
        print('안녕하세요. A입니다.')
 
class B(A):
    def greeting(self):
        print('안녕하세요. B입니다.')
 
class C(A):
    def greeting(self):
        print('안녕하세요. C입니다.')
 
class D(B, C):
    pass
 
x = D()
x.greeting()    # 안녕하세요. B입니다.
..
..
안녕하세요. B입니다.

image

메서드 탐색 순서 확인하기

많은 프로그래밍 언어들이 다이아몬드 상속에 대한 해결책을 제시하고 있는데 파이썬에서는 메서드 탐색 순서(Method Resolution Order, MRO)를 따릅니다.

>>> D.mro()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

참고 | object 클래스

파이썬에서 object는 모든 클래스의 조상입니다. 그래서 int의 MRO를 출력해보면 int 자기 자신과 object가 출력됩니다.

>>> int.mro()
[<class 'int'>, <class 'object'>]

파이썬 3에서 모든 클래스는 object 클래스를 상속받으므로 기본적으로 object를 생략합니다. 다음과 같이 클래스를 정의한다면

class X:
    pass
괄호 안에 object를 넣은 것과 같습니다.

class X(object):
    pass

추상 클래스 사용하기

파이썬은 추상 클래스(abstract class)라는 기능을 제공합니다. 추상 클래스는 메서드의 목록만 가진 클래스이며 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용합니다.

먼저 추상 클래스를 만들려면 import로 abc 모듈을 가져와야 합니다( abc는 abstract base class의 약자입니다). 그리고 클래스의 ( )(괄호) 안에 metaclass=ABCMeta를 지정하고, 메서드를 만들 때 위에 @abstractmethod를 붙여서 추상 메서드로 지정합니다.

from abc import *
 
class StudentBase(metaclass=ABCMeta):
    @abstractmethod
    def study(self):
        pass
 
    @abstractmethod
    def go_to_school(self):
        pass
 
class Student(StudentBase):
    def study(self):
        print('공부하기')
 
    def go_to_school(self):
        print('학교가기')
 
james = Student()
james.study()
james.go_to_school()
..
..
공부하기
학교가기

추상 메서드를 빈 메서드로 만드는 이유

그리고 또 한 가지 중요한 점이 있는데 추상 클래스는 인스턴스로 만들 수가 없다는 점입니다. 그래서 지금까지 추상 메서드를 만들 때 pass만 넣어서 빈 메서드로 만든 것입니다. 왜냐하면 추상 클래스는 인스턴스를 만들 수 없으니 추상 메서드도 호출할 일이 없기 때문이죠.

    @abstractmethod
    def study(self):
        pass    # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦
 
    @abstractmethod
    def go_to_school(self):
        pass    # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦

정리하자면 추상 클래스는 인스턴스로 만들 때는 사용하지 않으며 오로지 상속에만 사용합니다. 그리고 파생 클래스에서 반드시 구현해야 할 메서드를 정해 줄 때 사용합니다.

Leave a comment