Python

[python] 클래스 상속(inheritance)

justhash 2025. 4. 27. 16:27

앞장에 클래스 상속을 공부하다보니 필요한 클래스(class), 생서자(__init__) 추가로 (self)에 관하여 간략하게 정리를 진행해 보았다.

이제 본격적으로 클래스 상속에 관해 알아보자.

알아볼 내용:

상속, 생성자, super(), 다중 상속, 다중 상속시 인자 처리

 

클래스 '상속'이란?

: 부모 클래스(Super Class or Parent Class)의 속성(변수)과 메소드(함수)를 자식 클래스(Child Class or Sub Class)가 가지게 되는 메커니즘을 뜻한다.

쉽게 말하면 클래스를 만들때, 이미 만들어져 있는 클래스의 능력을 그대로 갖도록 하는 것이다.

 

아래 코드를 살펴보자.

class bumo:
    name = '부모님 이름'
    home = '서울'

    def show_info(self):
        print('부모님 정보 입니다.')
        print(self.name)
        print(self.home)
        
    def show_name(self):
        print("부모 클래스의 메소드")
        print(self.name)

class jasik(bumo):
    def __init__(self, name):
        self.name = name
        self.school = 'mid'
    
    def show_name(self):
        print("자식 클래스의 메소드")
        print(self.name)
    
    def show_jasik_info(self):
        print("자식의 정보 입니다.")
        print(self.name)
        print(self.school)
        print(self.home)

sh = jasik('hash')

sh.show_name()
>>> 자식 클래스의 메소드
>>> hash

sh.show_info()
>>> 부모님 정보 입니다.
>>> hash
>>> 서울

sh.show_jasik_info()
>>> 자식의 정보 입니다.
>>> hash
>>> mid
>>> 서울

 

1. 변수, method 오버라이드

 첫 번째 메소드 'show_name'은 'jasik'클래스가 상속 받고있는 'bumo'클래스에도 동일하게 정의되어있는 메소드다. 하지만, 상속을 받는 'jasik' 클래스에서 동일한 이름으로 메소드가 오버라이드 되었다. 따라서 해당 메소드는 'jasik'클래스에 정의되어있는 메소드가 실행되게 된다.

 

 두 번째 실행된, sh.show_info()의 결과를 살펴보면 메소드 오버라이드 없이 'bumo' 클래스의 함수가 실행됐지만 이름이 'hash'로 출력된 것을 확인해볼 수 있다. 이는 상속받은 변수 'name'이 'jasik' 클래스에서 역시 새로 정의되면서 오버라이드 된 것이다.

 

 또한, 출력된 값을 보면 'bumo' 클래스에 정의된 name이 hash로 출력된 것을 확인할 수 있다. 이는 상속받은 클래스에 동일한 이름의 변수'name'이 jasick클래스에도 동일하게 정의되어 오버라이드 된 것이다.

 

2. 변수, 메소드 상속

 두 번째 실행된 'show_info'함수는 'jasik' 클래스에는 정의되어있지 않다. 따라서, 상속받은 'bumo' 클래스에 접근하여 해당 메소드를 찾게되고, 'bumo' 클래스에 정의되어있는 메소드가 실행되는 것이다.

 

마지막 실행된 함수 'show_jasik_info'는 'jasik' 클래스에서 정의 되었다. 하지만 출력하는 변수 중 'self.home'은 자식 클래스에서 정의되지 않은 값이다. 하지만 에러 발생없이 출력된 값을 보면 '서울'이란 값이다. 이는 상속받은 'bumo' 클래스에서 가지고 있는 'home'이란 변수를 상속받은 것을 확인해볼 수 있다.

 

 

다중 상속

: 위의 코드 상으로는 상속받는 클래스에 생성자가 별도로 존재하지 않기도 하고, 단일 클래스를 상속받고 있다. 하지만, 여러개의 클래스를 상속받고, 각 클래스마다 생성자가 있다면 어떻게 해야할까?

 

1. 단일 클래스를 상속받는 경우 생성자.

위의 코드에서 자식 클래스의 생성자에 부모클래스의 생성자를 실행하는 코드를 추가하면 간단히 해결된다. (통상적으로, 생성자가 실행될때 함께 실행시킨다.)

아래 코드를 살펴보자

class bumo:
    name = '부모님 이름'
    home = '서울'
    def __init__(self, name):
        print("부모 생성자")
        bumo.name = name

    def show_info(self):
        print('부모님 정보 입니다.')
        print(self.name)
        print(self.home)

    def show_name(self):
        print("부모 클래스의 메소드")
        print(self.name)

class jasik(bumo, jobumo):
    def __init__(self, name, bumo_name):
        super().__init__(bumo_name)
        print("자식 생성자")
        self.name = name
        self.jasik_home = self.home
        self.school = 'mid'
    
    def show_name(self):
        print("자식 클래스의 메소드")
        print(self.name)
    
    def show_jasik_info(self):
        print("자식의 정보 입니다.")
        print(self.name)
        print(self.school)
        print(self.jasik_home)
        print(super().name)
        
sh = jasik('hash', 'ch')
sh.show_name()
sh.show_info()
sh.show_jasik_info()

############## 실행 결과 ##############

>>> 부모 생성자
>>> 자식 생성자
>>> 자식 클래스의 메소드
>>> hash
>>> 부모님 정보 입니다.
>>> hash
>>> 서울
>>> 자식의 정보 입니다.
>>> hash
>>> mid
>>> 서울
>>> ch

 

 '자식 생성자' 라는 텍스트가 출력되기 전에 부모 클래스의 생성자가 실행되어 '부모 생성자'가 출력된 것을 확인해볼 수 있다. 또한, 부모 클래스의 생성자를 실행할때 입력한 'bumo_name'값을 부모 클래스의 생성자에서 'bumo' 클래스의 클래스 변수에 저장한 것을 마지막 출력 결과를 통해 확인해볼 수 있다.

 여기서 확인할 수 있는 것은 부모 클래스(parent/super class)에 접근하는 방법은 'super()'를 통해 접근할 수 있는 것이다.

 

2. 복수(다중) 클래스를 상속받는 경우.

그렇다면 여러 클래스를 상속받는 경우는 어떻게 될까?

 

 일단 기본적인 메커니즘인 클래스 변수와, 메소드는 동일하게 상속되어 자식 클래스에서 사용이 가능하다. 아래 코드를 살펴보자.

class jobumo:
    name = '조부모님 이름'
    home = '경기도'
    def __init__(self, name, age):
        jobumo.name = name
        print("조부모 생성자")

    def show_jobumo_info(self):
        print('조부모님 정보 입니다.')
        print(self.name)
        print(self.home)
        print(jobumo.home)

    def show_name(self):
        print("조부모 클래스의 메소드")
        print(self.name)


class bumo:
    name = '부모님 이름'
    home = '서울'
    def __init__(self, name):
        print("부모 생성자")
        bumo.name = name

    def show_bumo_info(self):
        print('부모님 정보 입니다.')
        print(self.name)
        print(self.home)

    def show_name(self):
        print("부모 클래스의 메소드")
        print(self.name)

class jasik(bumo, jobumo):
    def __init__(self, name, bumo_name):
        super().__init__(bumo_name)
        print("자식 생성자")
        self.name = name
        self.jasik_home = self.home
        self.school = 'mid'
    
    def show_name(self):
        print("자식 클래스의 메소드")
        print(self.name)
    
    def show_jasik_info(self):
        print("자식의 정보 입니다.")
        print(self.name)
        print(self.school)
        print(self.jasik_home)
        print(super().home)

sh = jasik('hash', 'ch')
sh.show_name()
sh.show_bumo_info()
sh.show_jobumo_info()
sh.show_jasik_info()

############## 실행 결과 ##############

>>> 부모 생성자
>>> 자식 생성자
>>> 자식 클래스의 메소드
>>> hash
>>> 부모님 정보 입니다.
>>> hash
>>> 서울
>>> 조부모님 정보 입니다.
>>> hash
>>> 서울
>>> 경기도
>>> 자식의 정보 입니다.
>>> hash
>>> mid
>>> 서울
>>> 서울

 

 우선 상속받는 각 클래스에서 모두 메소드를 상속받은 것을 확인할 수 있다. (show_bumo_info(), show_jobumo_info())

 하지만, 마지막 출력 결과를 보면 super().home의 출력 결과가 '서울'인 것으로 보아 부모 클래스의 home값을 반환한 것을 확인해볼 수 있다. 또한, 생성자 역시 부모 생성자, 자식 생성자 만 출력되고, 조부모 생성자는 실행되지 않았다.

 코드를 추가하지는 않았지만, 자식 생성자의 상속 클래스를 정의할때 'bumo'와 'jobumo'의 순서를 바꿔 정의하면 에러가 발생한다. super().__init__을 실행할 때 입력 인자가 한개만 입력되어 발생한 에러이다. (jobumo 클래스의 생성자 인자는 2개 입력받아야 하기 때문에)

 이를 통해, super()를 통해 접근하는 상속 클래스는 정의된 가장 첫 번째 클래스인 것을 확인해볼 수 있다.

 

 다중 상속을 받은 경우에는 mro( Method Resolution Order )를 통해 클래스 상속 순서를 확인해야 한다. super()를 통해 접근하는 것은 이 mro를 통해 확인한 순서상 다음 클래스에 접근한다는 의미이다. 따라서, 다중 클래스의 모든 생성자를 실행하고 싶다면, 위 코드를 예시로 들면 'bumo'클래스에 super().__init__()을 실행해 줘야한다. (물론 'jobumo'클래스의 생성자 인자에 맞춰 인자를 입력해야 한다.)

따라서, 아래와 같이 코드를 실행하면 모든 클래스의 생성자를 실행할 수 있다.

class jobumo:
    name = '조부모님 이름'
    home = '경기도'
    def __init__(self, name, age):
        jobumo.name = name
        print("조부모 생성자")

    def show_jobumo_info(self):
        print('조부모님 정보 입니다.')
        print(self.name)
        print(self.home)
        print(jobumo.home)

    def show_name(self):
        print("조부모 클래스의 메소드")
        print(self.name)


class bumo:
    name = '부모님 이름'
    home = '서울'
    def __init__(self, name, bumo_name, bumo_age):
        super().__init__(bumo_name, bumo_age)
        print("부모 생성자")
        bumo.name = name

    def show_bumo_info(self):
        print('부모님 정보 입니다.')
        print(self.name)
        print(self.home)

    def show_name(self):
        print("부모 클래스의 메소드")
        print(self.name)

class jasik(bumo, jobumo):
    def __init__(self, name, bumo_name, jobumo_name, jobumo_age):
        super().__init__(bumo_name, jobumo_name, jobumo_age)
        # bumo().__init__(bumo_name)
        # super(jobumo).__init__(bumo_name, 60)
        print("자식 생성자")
        self.name = name
        self.jasik_home = self.home
        self.school = 'mid'
    
    def show_name(self):
        print("자식 클래스의 메소드")
        print(self.name)
    
    def show_jasik_info(self):
        print("자식의 정보 입니다.")
        print(self.name)
        print(self.school)
        print(self.jasik_home)
        print(super().home)

sh = jasik('hash', 'ch', 'joch', 60)
sh.show_name()
sh.show_bumo_info()
sh.show_jobumo_info()
sh.show_jasik_info()

print(jasik.mro())

############## 실행 결과 ##############

>>> 조부모 생성자
>>> 부모 생성자
>>> 자식 생성자
>>> 자식 클래스의 메소드
>>> hash
>>> 부모님 정보 입니다.
>>> hash
>>> 서울
>>> 조부모님 정보 입니다.
>>> hash
>>> 서울
>>> 경기도
>>> 자식의 정보 입니다.
>>> hash
>>> mid
>>> 서울
>>> 서울
>>> [<class '__main__.jasik'>, <class '__main__.bumo'>, <class '__main__.jobumo'>, <class 'object'>]

 

가장 마지막 출력 결과에서 확인할 수 있는 것이 클래스 상속 순서이다. (jasik -> bumo -> jobumo)