파이썬과 객체지향 프로그래밍 - 예제로 이해하는 객체지향 문법 (상속)

6. 예제로 이해하는 객체지향 문법 (상속)

6.1. Class Inheritance (상속)

  • 추상화(abstraction): 여러 클래스에 중복되는 속성, 메서드를 하나의 기본 클래스로 작성하는 작업
  • 상속(inheritance): 기본 클래스의 공통 기능을 물려받고, 다른 부분만 추가 또는 변경하는 것
    • 이 때 기본 클래스는 부모 클래스(또는 상위 클래스), Parent, Super, Base class 라고 부름
    • 기본 클래스 기능을 물려받는 클래스는 자식 클래스(또는 하위 클래스), Child, Sub, Derived class 라고 부름

  • 코드 재사용이 가능, 공통 기능의 경우 기본 클래스 코드만 수정하면 된다는 장점
  • 부모 클래스가 둘 이상인 경우는 다중 상속 이라고 부름

예: 사각형, 삼각형, 원 클래스

  • 공통점과 차이점 찾아보기
    • 사각형: 사각형 이름, 사각형 색, 사각형 너비/높이, 사각형 넓이
    • 삼각형: 삼각형 이름, 삼각형 색, 삼각형 한 변 길이, 삼각형 넓이
    • 원: 원 이름, 원 색, 원 반지름, 원 넓이
  • 부모 클래스를 자식 클래스에 인자로 넣으면 상속이 됨
    - 다음 코드는 __init__(self, name, color) 메서드가 상속되고,
    - self.name과 self.color 도 __init__ 실행시 생성됨
    
In [129]:
class Figure:
    def __init__(self, name, color):
        self.__name = name
        self.__color = color
In [130]:
class Quadrangle(Figure):
    def set_area(self, width, height):
        self.__width = width 
        self.__height = height

    def get_info(self):
        print (self.__name, self.__color, self.__width * self.__height)
In [131]:
square = Quadrangle('dave', 'blue')
square.set_area(5, 5)
square.get_info()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-131-6c4e1f315c4a> in <module>()
      1 square = Quadrangle('dave', 'blue')
      2 square.set_area(5, 5)
----> 3 square.get_info()

<ipython-input-130-2e60f91822e3> in get_info(self)
      5 
      6     def get_info(self):
----> 7         print (self.__name, self.__color, self.__width * self.__height)

AttributeError: 'Quadrangle' object has no attribute '_Quadrangle__name'
In [ ]:
class Quadrangle:
    def __init__(self, name, color):
        self.name = name
        self.color = color
        
    def set_area(self, width, height):
        self.__width = width 
        self.__height = height

    def get_info(self):
        print (self.name, self.color, self.__width * self.__height)
In [104]:
square = Quadrangle()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-104-34362c3785a3> in <module>()
----> 1 square = Quadrangle()

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


In [118]:
class Quadrangle(Figure):
    def set_area(self, width, height):
        self.__width = width 
        self.__height = height

    def get_info(self):
        print (self.name, self.color, self.__width * self.__height)
In [119]:
square = Quadrangle('dave', 'blue')
square.name
Out[119]:
'dave'
In [120]:
square = Quadrangle('dave', 'blue')
square.set_area(5, 5)
square.get_info()
dave blue 25

보며 다시 한번 이해해보기

  • 상속 관계인 클래스 확인하기
    • 내장함수 issubclass(자식 클래스, 부모 클래스) 사용하기
In [121]:
# Quadrangle 클래스가 Figure 클래스의 자식 클래스인지 확인
issubclass(Quadrangle, Figure)
Out[121]:
True
  • 클래스와 객체간의 관계 확인하기
    • 내장함수 isinstance(객체, 클래스) 사용하기
In [122]:
figure1 = Figure('figure1', 'black')
square = Quadrangle('square', 'red')


In [123]:
print(isinstance(figure1, Figure))
print(isinstance(square, Figure))
print(isinstance(figure1, Quadrangle))
print(isinstance(square, Quadrangle))
True
True
False
True
초간단 연습6
* 다음 세 코드가 실행된 후, 최종 값을 예상해보고, 왜 이와 같이 작성하였는지 이해하기(상속 개념 이해)
In [128]:
class Quadrangle(Figure):
    def set_area(self, width, height):
        self.__width = width 
        self.__height = height

    def get_info(self):
        print (self.name, self.color, self.__width * self.__height)
In [124]:
square = Quadrangle("square1", "black")
In [125]:
square.set_area(5, 5)
In [127]:
square.get_info()
square1 black 25

또 다른 예: 사람과 학생

In [132]:
# 클래스 선언
class Person:
    def __init__(self, name):
        self.name = name
        
class Student(Person):
    def study(self):
        print (self.name + " studies hard")

class Employee(Person):
    def work(self):
        print (self.name + " works hard")


In [133]:
# 객체 생성
student1 = Student("Dave")
employee1 = Employee("David")
In [134]:
# 객체 실행
student1.study()
employee1.work()
Dave studies hard
David works hard

6.2. Method Override (메서드 재정의)

  • 부모 클래스의 method를 자식 클래스에서 재정의(override)
  • 자식 클래스에서 부모 클래스 method를 재정의
  • 자식 클래스 객체에서는 재정의된 메소드가 호출됨
  • 자식 클래스에서 부모 클래스의 메서드와 이름만 동일하면 메서드 재정의가 가능함
    • C++/Java언어 등에서는 메서드와 인자도 동일해야 함
In [115]:
# 클래스 선언
class Person:
    def __init__(self, name):
        self.name = name

    def work(self):
        print (self.name + " works hard")        

class Student(Person):
    def work(self):
        print (self.name + " studies hard")
In [116]:
# 객체 생성
student1 = Student("Dave")
# 자식 클래스(Student)의 재정의된 work(self) 호출
student1.work()
Dave studies hard

Sub 클래스에 메서드를 더 추가할 수도 있죠!

In [135]:
class Person:
    def work(self):
        print('work hard')

class Student(Person):
    def work(self):
        print('Study hard')
        
    def go_to_school(self):
        print('Go to school')


In [136]:
p1 = Person()
s1 = Student()

p1.work()
#p1.go_to_school()

s1.work()
s1.go_to_school()
work hard
Study hard
Go to school
초간단 연습7
* Car class 만들기
- Car class
- attribute: 생성자에서 self.name 설정
- method: info(self) - 이름 출력

- Eletronic Car class
- attribute: 생성자에서 self.name 설정
- method overide: info(self) - 이름과 사용 연료(Eletronic) 출력

- Gasoline Car class
- attribute: 생성자에서 self.name 설정
- method overide: info(self) - 이름과 사용 연료(Gasoline) 출력
In [138]:
class Car:
    def __init__(self, name):
        self.name = name
    
    def get_info(self):
        print (self.name)

class ElecCar(Car):
    def get_info(self):
        print (self.name, 'Fuel: Eletronic')

        
class GasoCar(Car):
    def get_info(self):
        print (self.name, 'Fuel: Gasoline')

elec = ElecCar('dave')
gaso = GasoCar('david')
print (elec.get_info(), gaso.get_info())
dave Fuel: Eletronic
david Fuel: Gasoline
None None

6.3. 자식 클래스에서 부모 클래스 메서드 호출 (super 와 self)

super

  • 자식 클래스에서 부모 클래스의 method를 호출할 때 사용
    • super().부모 클래스의 method명
In [139]:
# 클래스 선언
class Person:
    def work(self):
        print('work hard')

class Student(Person):
    def work(self):
        print('Study hard')
        
    def parttime(self):
        super().work()
In [140]:
student1 = Student()
student1.work()
student1.parttime()
Study hard
work hard

self

  • self는 현재의 객체를 나타냄
    • self.method명 또는 attribute명 으로 호출함
  • C++/C#, Java언어에서는 this 라는 키워드를 사용함
In [142]:
# 클래스 선언
class Person:
    def work(self):
        print('work hard')

class Student(Person):
    def work(self):
        print('Study hard')
        
    def parttime(self):
        super().work()

    def general(self):
        self.work() 


In [143]:
student1 = Student()
student1.work()
student1.parttime()
student1.general()
Study hard
work hard
Study hard

6.4. 자식 클래스에서 부모 클래스 메서드 확장하는 방법 (심화)

  • 부모 클래스 메서드 기능에 추가적인 기능이 필요할 경우
    • 부모 클래스 메서드는 그대로 이용하면서 자식 클래스 메서드에서 필요한 기능만 정의하는 기법
    • 해당 메서드에서 상속 받은 부모 클래스명.메서드명 을 호출하고, 필요한 기능 추가 정의
In [8]:
# 클래스 선언
class Person:
    def work(self):
        print('work hard')

class Student(Person):
    def work(self):
        Person.work(self) # 부모 클래스 메서드 호출
        print('Study hard')
In [9]:
student1 = Student()
student1.work()
work hard
Study hard
In [44]:
class Figure:
    # 생성자(initializer)
    def __init__(self, width, height):
        # self.* : 인스턴스변수
        self.width = width
        self.height = height
        
    def print_info(self):
        print ('사이즈:', self.width, self.height)

class Rectangle(Figure):
    def print_info(self):
        Figure.print_info(self) # 부모 클래스 메서드 호출
        print ('너비:', self.width * self.height)
In [45]:
rectangle1 = Rectangle(2, 3)
rectangle1.print_info()
사이즈: 2 3
너비: 6

6.5. 추상 클래스 사용하기

  • 메서드 목록만 가진 클래스, 상속받는 클래스에서 해당 메서드 구현해야 함
  • 예: 게임에서 모든 캐릭터는 공격하기, 이동하기의 공통 기능을 가지고 있음
    • 공통 기능을 추상 클래스로 만들고, 각 세부 기능은 해당 메서드에서 구현하는 것

사용법

  • abc 라이브러리를 가져옴 (from abc import * )
  • 클래스 선언시 ( ) 괄호 안에 metaclass=ABCMeta 를 지정
  • 해당 클래스에서 메서드 선언시 상단에 @abstractmethod 를 붙여줘야 함


In [144]:
# 추상 클래스 선언하기
from abc import *

class Character(metaclass=ABCMeta):
    @abstractmethod
    def attack(self):
        pass
    
    @abstractmethod
    def move(self):
        pass
In [145]:
# 추상 클래스 상속하기
class Elf(Character):
    def attack(self):
        print ("practice the black art")
    
    def move(self):
        print ("fly")
        
class Human(Character):
    def attack(self):
        print ("plunge a knife")
    
    def move(self):
        print ("run")
In [146]:
# 객체 실행하기

elf1 = Elf()
human1 = Human()

elf1.attack()
elf1.move()
human1.attack()
human1.move()
practice the black art
fly
plunge a knife
run

다른 예

In [151]:
# 추상 클래스 선언하기
from abc import *

class Character(metaclass=ABCMeta):
    def __init__(self, hp):
        self.hp = hp
        
    def get_hp(self):
        return self.hp
    
    @abstractmethod
    def attack(self):
        pass
    
    @abstractmethod
    def move(self):
        pass
In [147]:
character = Character(10)  # 추상 클래스는 객체로 만들 수 없어요!!! 추상 메서드를 다 채워야 합니다.
------------------------------------------
TypeErrorTraceback (most recent call last)
<ipython-input-147-756b7c98e059> in <module>()
----> 1 character = Character(10)  # 추상 클래스는 객체로 만들 수 없어요!!! 추상 메서드를 다 채워야 합니다.

TypeError: object() takes no parameters
In [152]:
# 추상 클래스 상속하기
class Elf(Character):
    def attack(self):
        print ("practice the black art")
    
    def move(self):
        print ("fly")
In [153]:
character = Elf(10)
print(character.get_hp())
10


초간단 연습8
* Car class 추상 클래스 만들기
- attribute: 자동차 이름 - method: info(self) - 이름 출력
- abstract method: fuel(self)

- Eletronic Car class
- attribute: 생성자에서 self.name 설정
- method: info(self) - 이름 출력
- method: fuel(self) - 사용 연료(Eletronic) 출력
In [158]:
# 추상 클래스 선언하기
from abc import *

class Car(metaclass=ABCMeta):
    def __init__(self, name):
        self.name = name
    
    def get_info(self):
        return self.name 
    
    @abstractmethod
    def fuel(self):
        pass

class ElecCar(Car):
    def fuel(self):
        return 'Electronic'

elec_car = ElecCar('dave')
print (elec_car.get_info(), elec_car.fuel())
dave Electronic