Python의 Class와 상속(inheritance)의 개념

class Person:
    """Super Class"""
    # 클래스 변수
    total_count = 0
    
    # 생성자 메서드(method)
    def __init__(self):
        self.name = '홍길동'
        self.age = 1
        Person.total_count+=1
    
    # class내 정의된 메서드(method)
    def introduce(self):
        print(f'제 이름은 {self.name} 이고, 나이는 {self.age}살 입니다.')

인스턴스(instance) 생성과 객체(object)

객체는 바로 밑의 예시에서 p1, p2, p3를 일컫습니다.

정의된 클래스(class)로부터 생성된 녀석을 인스턴스(instance) 혹은 객체(object)라고 합니다.

하지만, 용어의 온도차(?)는 존재합니다.

p1 = Person()
p2 = Person()
p3 = Person()
p1.introduce()
제 이름은 홍길동 이고, 나이는 1살 입니다.

객체와 인스턴스의 차이

점프투 파이썬의 좋은 설명을 인용하도록 하겠습니다.


클래스로 만든 객체를 인스턴스라고도 한다.

그렇다면 객체와 인스턴스의 차이는 무엇일까?

이렇게 생각해 보자. p1 = Person() 이렇게 만든 p1객체이다.

그리고 p1 객체는 Person의 인스턴스이다.

인스턴스라는 말은 특정 객체(p1)가 어떤 클래스(Person)의 객체인지를 관계 위주로 설명할 때 사용한다.

“p1는 인스턴스”보다는 “p1은 객체”라는 표현이 어울리며 “p1는 Person의 객체”보다는 “p1은 Person의 인스턴스”라는 표현이 훨씬 잘 어울린다.

클래스 변수의 출력

클래스 변수는 모든 클래스가 공유하게 됩니다. 클래스의 객체가 3번 만들어 졌기 때문에 3이 출력되는 것을 확인할 수 있습니다.

p1.__class__.total_count
3
class Person:
    """Super Class"""
    # 클래스 변수
    total_count = 0
    
    # 생성자 메서드
    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.total_count+=1
    
    # class내 정의된 함수(method)
    def introduce(self):
        print(f'제 이름은 {self.name} 이고, 나이는 {self.age}살 입니다.')

다음을 실행시 오류가 발생합니다.

생성자 메서드가 재정의 되었기 때문입니다.

p4 = Person()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_3737434/2346419028.py in <module>
----> 1 p4 = Person()

TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
p5 = Person('김철수', 22)
p5.introduce()
제 이름은 김철수 이고, 나이는 22살 입니다.

클래스 상속 (inheritance) 받기

class Student(Person):
    """Sub Class"""

    def __init__(self):
        super().__init__()

    def print_name(self):
        print(f'제 이름은 {self.name} 입니다.')
        
    def print_age(self):
        print(f'제 나이는 {self.age} 입니다.')
s1 = Student()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_3737434/391622993.py in <module>
----> 1 s1 = Student()

/tmp/ipykernel_3737434/3151107577.py in __init__(self)
      3 
      4     def __init__(self):
----> 5         super().__init__()
      6 
      7     def print_name(self):

TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'

오류를 어떻게 해결할 수 있을까?

Sub Class 에서의 오류

Student 클래스는 Person 클래스를 상속받아 구현하게 되면서, 생성자 (init) 함수에서 name, age argument를 요구합니다. 이 Rule을 지켜주지 못하면 에러가 발생합니다.

첫 번째 해결책

super().__init__() 호출시 name과 age argument를 넘겨줍니다.

class Student(Person):
    """Sub Class"""

    def __init__(self):
        super().__init__('테디', 30)
student = Student()

두 번재 해결책

Student 클래스의 __init__().__init__(name, age) 인자를 받는 생성자 메서드로 재정의할 수 있습니다.

class Student(Person):
    """Sub Class"""

    def __init__(self, name, age):
        super().__init__(name, age)
student = Student('테디', 30)
student.introduce()
제 이름은 테디 이고, 나이는 30살 입니다.

메서드 오버라이딩

  • 메서드 오버라이딩은 부모로부터 물려받은(상속받은) 메서드를 재정의하여 사용할 때 사용합니다.
  • 부모로부터 물려받은 다른 기능은 그대로 사용하되, 몇몇 메서드만 수정하여 활용하고 싶을 때 사용합니다.
class Person:
    """Super Class"""
    
    # PErson의 생성자 메서드
    def __init__(self, name='홍길동', age=20):
        self.name = name
        self.age = age
    
    # Person의 메서드
    def introduce(self):
        print(f'난 Person이야. 내 이름은 {self.name} 이고, 나이는 {self.age}살이야.')

케이스 1. 클래스 메서드 오버라이딩을 안 하는 경우

class Student(Person):
    """Sub Class"""

    def __init__(self):
        super().__init__()

다음과 같이 부모클래스 (Super Class)의 introduce()가 실행됨을 확인할 수 있습니다.

student = Student()
student.introduce()
난 Person이야. 내 이름은 홍길동 이고, 나이는 20살이야.

케이스 2. 클래스 메서드 오버라이딩을 한 경우

class Student(Person):
    """Sub Class"""

    def __init__(self):
        super().__init__()

    # 메서드 오버라이딩
    def introduce(self):
        print(f'난 Student야. 내 이름은 {self.name}이고, 나이는 비밀이야.')

메서드 오버라이딩을 하게 되면, 자식클래스 (Sub Class)에서 재정의 한 메서드가 호출 되게 됩니다. (부모클래스의 메서드는 무시됩니다.)

student = Student()
student.introduce()
난 Student야. 내 이름은 홍길동이고, 나이는 비밀이야.

케이스 3. 클래스 메서드 오버라이딩을 하고 그 안에서 super()를 호출한 경우

class Student(Person):
    """Sub Class"""

    def __init__(self):
        super().__init__()

    # 메서드 오버라이딩
    def introduce(self):
        # 부모의 메서드 호출
        super().introduce()
        print(f'난 Student야. 내 이름은 {self.name}이고, 나이는 비밀이야.')

만약, 부모클래스의 메서드를 호출하고 싶다면, super().introduce() 형식으로 호출 할 수 있습니다.

student = Student()
student.introduce()
난 Person이야. 내 이름은 홍길동 이고, 나이는 20살이야.
난 Student야. 내 이름은 홍길동이고, 나이는 비밀이야.

상속 구조 확인

  • 상속의 구조는 클래스명.mro()로 확인할 수 있습니다.
  • 구조는 상속 받은 순서대로 표시됩니다.
  • 모든 class는 object를 상속받기 때문에 항상 object가 마지막에 표기 됩니다.
Student.mro()
[__main__.Student, __main__.Person, object]