상속 inheritance

상속은 기존 클래스의 성질을 이어 받아 새로운 클래스를 만드는 방법입니다. 기존 클래스를 기반 클래스 base class 또는 상위 클래스 superclass 또는 부모 클래스라고 하고 새로운 클래스를 파생 클래스 derived class 또는 하위 클래스 subclass 또는 자식 클래스라고 합니다.

클래스에서 상속이 가져다주는 장점은 다음과 같습니다.

  • 클래스의 간결화: 필드의 중복 작성 불필요

  • 클래스 관리 용이: 클래스들의 계층적 분류

  • 소프트웨어의 생산성 향상: 클래스 재사용과 확장 용이

자식이 부모로부터 재산을 상속받듯이 자식 클래스도 부모 클래스가 가지고 있는 속성(데이터, 메소드)들을 물려받습니다. 구현하려고 하는 여러 클래스들의 공통된 속성을 부모클래스에 정의하고, 상속받는 클래스들에서는 자신들만의 특성에 맞는 데이터와 메소드들을 구현합니다.

예를 들면, 학생이라는 클래스가 있으면 학생은 사람이므로 사람이 가지는 공통 속성들, 이름, 성별, 나이등을 상속을 받고 학생 객체의 특징인 소속학과, 학번등을 새롭게 추가하여 사용합니다. 이렇게하면 사람 클래스를 이용해서 중복 사용되는 부분을 줄일 수 있습니다. 만일 교직원 클래스를 만들려고 할 때도 마찬가지로 사람 클래스로부터 상속받으면 공통된 부분에 대해서 중복을 피할 수 있는 것입니다.

상속 만들기

<자식클래스이름>(부모클래스)와 같이 자식클래스와 괄호 안에 부모클래스를 적어서 상속을 받습니다. Person 클래스를 부모클래스로 하고 Student 클래스를 자식클래스로하는 예를 들면 다음과 같습니다.

In [1]: class Person:
   ...: 
   ...:   def __init__(self, name, age, gender):
   ...:     self.name = name
   ...:     self.age = age
   ...:     self.gender = gender
   ...: 
   ...:   def who(self):
   ...:     print("name: {}, age: {}, gender: {}".format(self.name, self.age, self.gender))
   ...: 
   ...: class Student(Person):
   ...: 
   ...:   def __init__(self, name, age, gender, major, id):
   ...:     self.name = name
   ...:     self.age = age
   ...:     self.gender = gender
   ...:     self.major = major
   ...:     self.id = id
   ...: 
   ...: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ...: kildong.who()
   ...: 
name: 홍길동, age: 28, gender: 남

상속 리스트가 없는 클래스는 기본으로 object 클래스를 상속받습니다.

부모클래스 생성자 부르기

위에서 Student 클래스에서 이름, 나이, 성별은 Person 클래스와 중복되는 속성입니다. 이 부분을 부모클래스의 생성자를 호출함으로 중복 코드를 제거합니다. 부모클래스의 생성자를 부르는 방법은 부모클래스이름.__init__(self) 메소드를 부르든지 super().__init__() 부르면 됩니다. 여기서 super()는 부모클래스의 객체를 의미합니다. super().__init__() 메소드에 첫번째 인수로 self는 자동으로 입력되므로 필요없습니다.

In [2]: class Person:
   ...: 
   ...:     def __init__(self, name, age, gender):
   ...:         self.name = name
   ...:         self.age = age
   ...:         self.gender = gender
   ...: 
   ...:     def who(self):
   ...:         print("name: {}, age: {}, gender: {}".format(self.name, self.age, self.gender))
   ...: 
   ...: class Student(Person):
   ...: 
   ...:     def __init__(self, name, age, gender, major, id):
   ...:         Person.__init__(self, name, age, gender)
   ...:         self.major = major
   ...:         self.id = id
   ...: 
   ...: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ...: kildong.who()
   ...: 
name: 홍길동, age: 28, gender: 남

직접하기

  1. Animal 클래스를 구현합니다. age 속성을 설정하세요. speak() 메소드를 추가하세요.

def speak(self):
  print('동물은 소리를 냅니다')
  1. Animal 클래스를 상속하여 Pig 클래스를 만들어 보세요. Pig 클래스의 인스턴스를 만들고 메소드들을 실행해보세요.

  2. super() 함수를 이용해서 부모클래스의 생성자를 불러봅니다.

자식 인스턴스를 만들 때 자식의 __init__ 메소드가 정의되어 있지 않으면 부모의 __init__ 메소드를 실행합니다.

In [3]: class Parent:
   ...:   def __init__(self):
   ...:     print('parent init')
   ...: 
   ...: class Child(Parent):
   ...:   pass
   ...: 
   ...: c1 = Child()
   ...: 
parent init

메소드 추가하기

부모클래스를 상속하면 자식클래스는 부모의 변수와 메소드를 모두 사용할 수 있게 됩니다. 자식클래스가 부모클래스에 없는 기능이 필요하면 메소드를 추가할 수 있습니다.

In [4]: class Student(Person):
   ...: 
   ...:   def __init__(self, name, age, gender, major, id):
   ...:     Person.__init__(self, name, age, gender)
   ...:     self.major = major
   ...:     self.id = id
   ...: 
   ...:   def info(self):
   ...:     print("major: {}, id: {}".format(self.major, self.id))
   ...: 
   ...: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ...: kildong.info()
   ...: 
major: 수학과, id: 20171101

메소드 재정의(overriding)

부모클래스의 메소드가 적당치 않으면 자식클래스에서 같은 이름으로 다르게 정의해서 사용할 수 있습니다.

In [5]: class Student(Person):
   ...: 
   ...:   def __init__(self, name, age, gender, major, id):
   ...:     super().__init__(name, age, gender)
   ...:     self.major = major
   ...:     self.id = id
   ...: 
   ...:   def info(self):
   ...:     print("major: {}, id: {}".format(self.major, self.id))
   ...: 
   ...:   def who(self):
   ...:     print("name: {}, age: {}, gender: {}, major: {}, id: {}".format(self.name, self.age, self.gender, self.major, self.id))
   ...: 
   ...: kildong = Student("홍길동", 28, "남", "수학과", 20171101)
   ...: kildong.who()
   ...: 
name: 홍길동, age: 28, gender: 남, major: 수학과, id: 20171101

C++, Java에서 메소드 재정의(method overriding)는 메소드 이름과 매개변수, 반환값이 모두 같아야하지만 파이썬은 이름만 같아도 됩니다.

직접하기

  1. Person 클래스에 메소드를 추가하고 Student 클래스에서 메소드 재정의를 해서 사용해봅니다.

  2. Student 클래스 재정의된 메소드의 매개변수를 바꿔서 실행해봅니다.

  3. 위에서 정의한 Pig, Dog 클래스의 speak() 메소드를 재정의 해보세요.

  4. User 클래스를 상속하여 Admin 클래스를 만들고 can add post, can delete post, can ban user 등의 문자열을 포함하는 privileges 리스트를 속성으로 추가하세요. show_privileges() 메소드를 추가하여 권한들을 출력하도록 하세요. 인스턴스를 만들어 메소드를 실행해보세요.

  5. Privileges 클래스를 만들어 privileges 속성을 추가하고 Admin에 정의되었던 show_privileges() 메소드를 옮겨 주세요. Admin 클래스에 Privileges 인스턴스를 만들어 속성을 대체하세요. Admin 인스턴스를 만들어 권한을 출력해보세요.

__str__() 메소드

__str__ 메소드를 이용하면 클래스 출력값을 변경할 수 있습니다. 다음과 같이 Person 클래스에 __str__ 메소드를 재정의합니다.

class Person:

    def __init__(self, 이름, 나이, 성별):
        self.이름 = 이름
        self.나이 = 나이
        self.성별 = 성별

    def __str__(self):
        return "저는 {}입니다. 나이는 {}세, {}입니다.".format(self.이름, self.나이, self.성별)

Person 클래스의 인스턴스를 직접 출력해봅니다.

영희 = Person('김영희', 25, '여자')
print(영희)
저는 김영희 입니다. 나이는 25, 여자 입니다.

상속과 이름공간

위에서 인스턴스 객체를 통해 변수나 함수의 이름을 찾는 규칙은 인스턴스 객체 영역 -> 클래스 객체 영역 -> 전역 영역 순서였습니다. 상속 관계에 있는 클래스의 인스턴스 객체의 이름 규칙은 인스턴스 객체 영역 -> 클래스 객체 영역 -> 부모 클래스 객체 영역 -> 전역 영역 순입니다.

변수 = "전역 변수"

class 부모:
    변수 = "부모변수"

    def __init__(self):
        pass

    def 메소드(self):
        print("부모 메소드")

class 자식(부모):
    변수 = "자식 변수"

    def __init__(self):
        pass

    def 메소드(self):
        print("자식 메소드")

 = 자식()
.메소드()
자식 메소드

직접하기

  • 자식 클래스의 메소드()에서 부모 클래스의 메소드()를 불러봅니다.

  • 자식 클래스의 메소드() 안에 다음 문장을 넣고 실행해보고 이야기 해봅니다.

    print(변수)
    print(self.변수)
    
  • 자식 클래스의 메소드() 안에서 부모 클래스의 변수를 출력해봅니다.