파이썬 클래스 상속

딥러닝견습생·2022년 1월 4일
0

1. 클래스의 상속

클래스를 상속한다. 부모 클래스를 자식이 상속한다.
(부모 재산을 자식이 상속받는다) 부모는 자식의 상위개념

코드로 상속은 이렇게 표현한다.

class Parent:
    pass

class Child(Parent):  # 자식이 부모를 상속받았다.
    pass

2. 상속 활용하기

상속여부 확인해보기

정말 상속이 되는지 확인해보자

class Parent:
    finance = 500000000  # 5억

    def greeting(self):
        print("안녕하세요")

    def my_finance(self):
        print(f"재산은 {self.finance}원 입니다")

class Child(Parent):
    pass

c = Child()
print(c.finance)  # 500000000
c.greeting()  # 안녕하세요
c.my_finance()  # 재산은 500000000원 입니다

# 상속여부 확인
issubclass(Child, Parent)  # True

자식 클래스인 Child 에는 변수 finance, 함수 greeting(), my_finance()를 선언해주지 않았음에도 자식이 그대로 상속받아 사용할 수 있다.

생성자, 비공개 변수, 비공개 메소드, staticmethod, classmethod 상속여부 확인해보기

import datetime

class Human:
    name = "김민국"  # public var
    __born = "한국"  # Private var

    def __init__(self, name, born):
        print("Human.__init__")
        self.set_public_name(public_name)
        self.__set_born(born)

    def display_name(self):
        print(f"제 이름은 {self.name}, 태어난 곳은 {self.__born} 입니다")

    def set_name(self, name):
        self.name = name
        print(f"제 이름은 {self.name} 입니다")

    def __set_born(self, born):
        self.__born = born
        print(f"전 {self.__born} 에서 태어났어요")

    @staticmethod
    def birth_to_age(year):
        return datetime.datetime.now().year - year

    @classmethod
    def korean_age(cls, year):
        return cls.birth_to_age(year) + 1
               # Human.birth_to_age(year) + 1

class Student(Human):
    def __init__(self, name):
        print("Student.__init__")
        self.set_name(name)

    def my_born(self):
        print(f"제가 태어난 곳은 {self.__born} 입니다")

s = Student("홍길동")
>>> Student.__init__  # Human.__init__ 은 호출되지 않았다

s.my_born()
>>> AttributeError: 'Student' object has no attribute '_Student__born'
# 자식이 부모의 private 변수에 직접 접근할 수 없다

s.display_name()
>>> 제 이름은 홍길동, 태어난 곳은 한국 입니다
# 부모의 함수를 통해서만 부모의 private 에 접근이 가능하다

print(Student.birth_to_age(1999))
>>> 23  # Student를 생성하지 않고도 함수를 사용한다. @staticmethod 상속됨

print(Student.korean_age(1999))
>>> 24  # birth_to_age() 사용하고 +1 한다. @classmethod 상속됨

상속을 거부할 순 없을까?

import random
class Parent:
    finance = 500000000  # 5억원
    
    def greeting(self):
        print("안녕하세요")

    def my_finance(self):
        print(f"재산은 {self.finance}원 입니다")

class Child(Parent):
    finance = 0  # 자식의 재산 재정의

    def greeting(self):  # 자식의 인사법 재정의(오버라이딩)
        greeting_msgs = ["안녕", "하이", "ㅎㅇ", "hi", "왔냐", "오랜만"]
        random.shuffle(greeting_msgs)
        print(greeting_msgs[0])

c = Child()
print(c.finance)
c.greeting()  # 하이  // 부모는 안녕하세요
c.my_finance()  # 재산은 0원 입니다

자식이 부모를 부른다. super()

class Parent:
    finance = 500000000
    
    def greeting(self):
        print("안녕하세요")

    def my_finance(self):
        print(f"재산은 {self.finance}원 입니다")

class Child(Parent):
    finance = 0  # 자식의 재산 재정의

    def greeting(self):  # 자식의 인사법 재정의(오버라이딩)
        print("하이!")

    def my_parents_finance(self):
        print(f"부모의 재산은 {super().finance}원 입니다.\n내 재산은 {self.finance}원 입니다")

c = Child()
c.my_parents_finance()

>>> Output
부모의 재산은 500000000원 입니다.
내 재산은 0원 입니다

주의! 부모가 자식의 메소드를 사용할 순 없다.
p = Parent()
p.my_parents_finance()
>>> AttributeError: 'Parent' object has no attribute 'my_parents_finance'

attribute: 클래스 내 메서드나 변수

실전 상속 활용하기(list 클래스 확장)

"""
리스트를 정말 많이 활용하는데..
원래 정의된 리스트는 손대지 않으면서 내가 원하는 부분만 손대보자(확장!)
"""
a = list()
# IDE 에서 Ctrl(Command) + 클릭 해보면 선언 되어있는 곳으로 이동할 수 있다.

# list가 정의되어있는 파일을 따라가보면..
class list(object):  # 클래스로 선언되어 있다
    ...  # 기본 메소드들 정의
    def append(self, *args, **kwargs):
        """ Append object to the end of the list. """
        pass

# 확장 클래스 정의
class MyList(list):  # 리스트를 상속하는 MyList를 만든다
    def append(self, *args, **kwargs):
        # 특별한 경우가 아니라면 부모의 인자들은 모두 보존하자
        print("append가 호출됐어요")

my_list = MyList()  # my_list = list()
my_list.append(10)
print(my_list)  # []

list 클래스를 상속받아 append() 메소드를 오버라이딩 했더니, 원래 정의된 .append()가 정의되지 않았다.

print() 문을 유지하면서도, 부모의 append() 기능을 그대로 사용하려면

class MyList(list):  # 리스트를 상속하는 MyList를 만든다
    def append(self, *args, **kwargs):
        super().append(*args, **kwargs)  # 부모의 .append()를 그대로 호출
        print("append가 호출됐어요")

my_list = MyList()
my_list.append(10)
print(my_list)
>>> Output
append가 호출됐어요  # 자식이 출력한 구문
[10]  # 본래 기능이 보존됐다

부모의 기능도 보존하고, 내 의도도 반영되었다.

그럼 이 append() 기능을 조금 변형시켜보자. append를 반복할 횟수를 지정해보면 어떨까?

class MyList(list):  # 리스트를 상속하는 MyList를 만든다
    def append(self, *args, **kwargs):
        if "loop" in kwargs:  # kwargs에 loop=n 형식으로 넘어왔다면
            loop = kwargs["loop"]  # 할당하고
            del kwargs["loop"]  # 지워줘야 한다. 본래 부모의 append는 loop를 사용하지 않는다.
            for i in range(loop):
                super().append(*args, **kwargs)
        else:
            super().append(*args, **kwargs)

my_list = MyList()
my_list.append(1)  # 1을 한번 넣고
my_list.append(5, loop=5)  # 5를 5번 넣을래
print(my_list)  # [1, 5, 5, 5, 5, 5]  # 1 한번, 5는 다섯번

상속의 순서, 다중상속

클래스는 연쇄 상속이 가능하고, 한번에 여러 클래스를 상속할 수 있다.

class Human:
    pass

class Parent(Human):
    pass

class Child(Parent):
    pass

print(Child.mro())  # 해당 클래스가 어떤 순서로 호출되는지 확인할 수 있다.
>>> [
  <class '__main__.Child'>, 
  <class '__main__.Parent'>,
  <class '__main__.Human'>,
  <class 'object'>
]

한번에 여러 클래스를 상속할 수 있다

class Human:
    pass

class JamMin:
    pass

# Parent와 JamMin 두개 클래스를 상속한다.
# 순서는 왼쪽(우선) -> 오른쪽
class Child(Human, JamMin):
    pass

print(issubclass(Child, JamMin))  # True
print(issubclass(Child, Human))  # True

print(Child.mro())  # 해당 클래스가 어떤 순서로 호출되는지 확인할 수 있다.
>>> [
  <class '__main__.Child'>,
  <class '__main__.Human'>,
  <class '__main__.JamMin'>,
  <class 'object'>
]

0개의 댓글