<개인공부>/[Python]

[Python] 파이썬 클래스2 상속, 추상 클래스, 메서드 오버라이딩

BlockDMask 2020. 11. 29. 00:30
반응형

안녕하세요. BlockDMask 입니다.
오늘은 지난시간에 이어서 파이썬 class 2탄 입니다.
오늘 배워볼것은 상속에 대한것 인데요. 상속도 굉장히 중요한 개념이니 꼭 알고 넘어 가시길 바랍니다.

지난시간의 클래스 : 파이썬 클래스 객체 생성자 메서드 포스팅 [바로가기]

<목차>
1. 클래스 상속 (class inheritance)
2. 메서드 오버라이딩 (method overriding)
3. 추상 클래스 (abstract class)


1. 파이썬 클래스 상속


클래스는 상속이라는 것을 할 수 있습니다.

상속(inheritance)라는 것은 어떤것을 물려 받을때 상속한다 혹은 상속 받는다고 하는데요. "건물을 상속 받다" 이렇게 쓰이죠? 
네 저도 건물 상속 .. 받고싶네요. 아빠가 숨겨둔 건물을 줄때가 되었는데 ....

즉 A라는 틀이 있는데 A라는 틀의 기능에 더해서 다른 기능을 추가하고 싶을때 B라는 틀이 A라는 틀의 변수와 메서드를 그대로 상속받고, B의 틀에 맞는 기능을 더 추가해서 만들 수 있습니다. 이러한 것을 상속 이라 부릅니다.

상속을 받는 클래스를 자식 클래스라하고, 상속해 주는 클래스를 부모클래스라 합니다. 코드에서는 아래와 같이 표현하여서 상속을 받습니다.

1
2
3
4
5
6
class 부모클래스:
    # ...
 
 
class 자식클래스(부모클래스):
    # ...
cs

이런식으로 클래스를 정의하면 자식 클래스는 부모클래스에 있는 내용들을(속성, 메서드) 상속 받을 수 있습니다. 실제로 한번 그런지 예제를 통해서 확인해보겠습니다.

폰이라는 부모클래스 하나를 두고 갤럭시 클래스와 아이폰 클래스를 자식 클래스로 만들어 보겠습니다. 우리에게 친근한 phone 이기 때문에 이해가 잘 갈 것 같아서 예제로 만들어 보갔습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 부모 클래스
class Phone:
    phone_name = '피쳐폰'
 
    def __init__(self, my_number, my_name):
        print('Phone 생성자 호출')
        self.my_number = my_number
        self.my_name = my_name
 
    def call(self, number):
        print(f'Phone 전화걸기 : {number}')
 
    def send_msg(self, number, msg):
        print(f'Phone 메시지 보내기 : {number}, {msg}')
 
    def get_info(self):
        print(f'Phone 내 번호 : {self.my_number}')
        print(f'Phone 내 이름 : {self.my_name}')
 
 
# 자식 클래스 1 : 사과폰
class ApplePhone(Phone):
    def __init__(self, my_number, my_name):
        super(ApplePhone, self).__init__(my_number, my_name)  # 부모 클래스 생성자 호출
        print('ApplePhone 생성자 호출')
 
    def button_home(self):
        print('ApplePhone 홈 버튼 눌림')
 
 
# 자식 클래스 2 : 우주폰
class GalaxyPhone(Phone):
    def __init__(self, my_number, my_name):
        super(GalaxyPhone, self).__init__(my_number, my_name)  # 부모 클래스 생성자 호출
        print('GalaxyPhone 생성자 호출')
 
    def button_cancel(self):
        print('GalaxyPhone 취소(뒤로가기) 버튼이 눌렸습니다')
 
 
# 클래스 생성
print('\n1. 각 클래스의 객체 생성')
phone = Phone('010-2222-2222''폰은정')
apple = ApplePhone('010-1234-5678''김멸치')
galaxy = GalaxyPhone('010-4321-4321''김근육')
 
# 부모 클래스로 부터 물려받은 메서드
print('\n2. 부모 클래스의 메서드 호출')
phone.get_info()
apple.get_info()
galaxy.get_info()
 
# 본인 메서드
print('\n3. 자식 클래스의 메서드 호출')
apple.button_home()
galaxy.button_cancel()
 
cs

  이렇게 하나의 Phone 클래스를 상속받은 ApplePhone 클래스와 GalaxyPhone 클래스를 만들어 보았습니다. 번호와 폰 주인의 이름을 변수로 저장하고 있는 속성과 전화를 걸고, 메시지를 주고 받는 메서드는 두 폰 모두 공통이기 때문에 부모로 부터 상속을 받습니다.
  그리고 
ApplePhone 고유 버튼인 홈 버튼 기능 GalaxyPhone 고유 버튼인 취소(뒤로가기) 버튼 기능을 따로 또 만들어 줍니다.

그렇게 함으로서 공통 부분은 Phone으로 상속 받아서 사용이 가능하고, 각자의 클래스에 맞게 또다른 변수(속성)나, 메서드(함수)를 만들어서 상황에 맞게 유연하게 만들 수 있습니다.

결과를 보실때 보셔야할것이 다섯가지가 있습니다.
1) 상속 받는 모양
-  자식 클래스(부모클래스):

2) 자식 클래스 생성자에서 부모클래스의 생성자를 불러주는 방법
- super(자식클래스, self).__init__(부모클래스 생성자 매개변수)

여기서 보면 
super(ApplePhone, self).__init__(my_number, my_name) 이런식으로 부모클래스의 생성자를 호출하여, 번호와 이름을 초기화 하였습니다.
부모클래스의 생성자를 자식 클래스에서 명시적으로 호출하려면 이렇게 사용하시면 됩니다.
** 만약 자식클래스의 생성자에서 따로 할일이 없다면, 자식 클래스 생성자는 따로 만들지 않아도 됩니다.
그렇게 되면 부모 클래스의 생성자가 내부적으로 알아서 불립니다. 
하지만 자식 클래스의 생성자를 만들게 되면 부모 클래스의 생성자는 따로 불리지 않습니다. 그렇기 때문에 부모 클래스의 생성자를 명시적으로 불러주어야만 합니다. 위 예제에서는 명시적으로 불러 주었습니다.

3) 클래스 생성 방법은 자식클래스도 부모클래스도 동일합니다.
- 클래스로 생성할 객체 이름 = 클래스(매개변수들)

4) 자식 클래스에서 부모클래스의 매개변수, 메서드는 자유롭게 사용하고 있는 것을 볼 수 있습니다.
- apple.get_info(), galaxy.get_info()

5) 예제의 맨 마지막을 보면 자식 클래스들이 본인 입맛에 맞게 생성한 메서드들을 본인 클래스에서 사용하고 있는것을 볼 수 있습니다.
- apple.button_home(), galaxy.button_cancel()


만약 이 클래스들을 상속이 아닌 각자 클래스를 만들게 되면 중복된 함수를 여러개 만들어 주어야겠죠? 마치 아래처럼요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 자식 클래스 1 : 사과폰
class ApplePhone:
    def __init__(self, my_number, my_name):
        # 중복
        self.my_number = my_number
        self.my_name = my_name
        print('ApplePhone 생성자 호출')
 
    # 중복
    def call(self, number):
        print(f'Phone 전화걸기 : {number}')
 
    # 중복
    def send_msg(self, number, msg):
        print(f'Phone 메시지 보내기 : {number}, {msg}')
 
    # 중복
    def get_info(self):
        print(f'Phone 내 번호 : {self.my_number}')
        print(f'Phone 내 이름 : {self.my_name}')
 
    # 고유 메서드
    def button_home(self):
        print('ApplePhone 홈 버튼 눌림')
 
 
# 자식 클래스 2 : 우주폰
class GalaxyPhone:
    def __init__(self, my_number, my_name):
        # 중복
        self.my_number = my_number
        self.my_name = my_name
        print('GalaxyPhone 생성자 호출')
 
    # 중복
    def call(self, number):
        print(f'Phone 전화걸기 : {number}')
 
    # 중복
    def send_msg(self, number, msg):
        print(f'Phone 메시지 보내기 : {number}, {msg}')
 
    # 중복
    def get_info(self):
        print(f'Phone 내 번호 : {self.my_number}')
        print(f'Phone 내 이름 : {self.my_name}')
 
    # 고유 메서드
    def button_cancel(self):
        print('GalaxyPhone 취소(뒤로가기) 버튼이 눌렸습니다')
 
 
# 클래스 생성
print('\n1. 클래스 생성')
apple = ApplePhone('010-1234-5678''김멸치')
galaxy = GalaxyPhone('010-4321-4321''김근육')
 
# 중복된 메서드
print('\n2. 중복된 메서드')
apple.get_info()
galaxy.get_info()
 
# 각자 다른 메서드
print('\n3. 각자 다른 메서드')
apple.button_home()
galaxy.button_cancel()
 
cs

이렇게 보면 ApplePhone, GalaxyPhone 클래스에 같은 기능을 하는 중복된 속성과 메서드가 있습니다. 지금이야 클래스가 2개 밖에 안되지만 여러 "~~~Phone" 클래스가 생기게 된다면 같은 기능을 하는 메서드와 속성을 계속 각각 만들어 주어야 하기 때문에 매우 비효율적으로 보입니다.

만들어 주는거 까지는 어찌어찌 노가다 하면서 복붙했다고 가정해봅시다.
그럼 나중에 call() 함수를 변경해야한다고 한다면 각 ~~~Phone 클래스 마다 전부 하나씩 바꾸어 주어야합니다. 즉, 유지보수 측면에서도 매우 비효율 적이라 판단이 됩니다.

이때 상속을 이용해서 설계, 구현을 했다면 공통 함수인 call 함수의 기능이 바뀌어도 부모 클래스인 Phone 클래스에 있는 call 함수만 바꿔주면 상속을 받은 "~~~Phone"클래스에도 적용이 한번에 되기 때문에 각 클래스마다 중복되는 함수를 계속 만드는 것 보다는 상대적으로 효율적이고 유용하게 작업이 가능하게 됩니다.

하지만 프로그래밍에는 정답이 없습니다. 본인의 입맛에 맞게 설계, 구현하면됩니다. 처음에는 상속을 생각해서 설계하기 쉽지 않습니다. 그러면 클래스들을 일단 구현해 보고 상속으로 묶을 수 있는가?를 역으로 생각해 보는것도 하나의 방법 같습니다.

어떤가요? 클래스 상속 유용하겠죠?


2. 파이썬 메서드 오버라이딩


메서드 오버 라이딩이란 method overriding 이라 해서 부모 클래스에 있는 메서드를 동일한 이름으로 다시만드는 것을 말합니다. 즉, 메서드를 덮어 씌우는 과정입니다. 또 다른 말로는 재정의 한다고도 합니다.

메서드를 자식클래스에서 재정의 하면, 자식클래스에서 해당 메서드를 호출했을 때 부모 클래스의 메서드는 가려지고 재정의한 자식 클래스의 메서드가 호출이 됩니다.

이게 왜 필요한가 하면, 자식 클래스에서 부모클래스에 정의된 함수를 사용하려하는데, 의도가 100프로 맞지 않는 경우가 있습니다. 이런경우에 재정의해서 사용하면 자식 클래스에 100프로 맞게 다시 정의 할 수 있습니다.

혹은 부모클래스를 설계할때, "아 이건 자식 클래스에서 꼭 재정의 했으면 좋겠다" 싶어서 함수를 일부러 비워두고 만드는 경우도 있습니다. (=추상메서드)

이렇게 자식 클래스에서 부모 클래스의 메서드를 재정의 하면 부모 클래스의 함수는 덮어 띄워져서 호출 되지 않게 됩니다.
의도적으로 자식 클래스에서 재정의 한 메서드를 호출해주지 않는 한 부모 클래스의 메서드는 가려지게 됩니다.

예제를 한번 볼까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Phone:
    def __init__(self, my_number, my_name):
        self.my_number = my_number
        self.my_name = my_name
        print('Phone 생성자 호출')
 
    def call(self, number):
        print(f'Phone 전화걸기 : {number}')
 
    def send_msg(self, number, msg):
        print(f'Phone 메시지 보내기 : {number}, {msg}')
 
    def get_info(self):
        print(f'Phone 내 번호 : {self.my_number}')
        print(f'Phone 내 이름 : {self.my_name}')
 
 
# 자식 클래스 : 사과폰
class ApplePhone(Phone):
    def button_home(self):
        print('ApplePhone 홈 버튼 눌림')
 
 
# 자식 클래스 : 우주폰
class GalaxyPhone(Phone):
    def button_cancel(self):
        print('GalaxyPhone 취소(뒤로가기) 버튼 눌림')
 
    # 메서드 오버라이딩
    def get_info(self):
        print('GalaxyPhone get_info')
        print(f'GalaxyPhone {self.my_name} {self.my_number}')
 
 
# 생성자 오버라이딩
print('\n객체 생성')
apple = ApplePhone('010-4321-4321''김사과')
galaxy = GalaxyPhone('010-2222-2222''김우주')
 
# 메서드 오버라이딩
print('\n메서드 오버라이딩')
apple.get_info()
galaxy.get_info()
 
cs

자식클래스(GalaxyPhone)에서 부모 클래스(Phone)에 있는 get_info 함수를 재정의(메서드 오버라이딩) 해보았습니다.

ApplePhone 객체에서 get_info 함수를 호출하니 부모 클래스의 get_info 함수가 호출이 되는것을 볼 수 있습니다.
GalaxyPhone 객체에서 get_info 함수를 호출하니 본인 클래스에서 재정의한 get_info 함수가 호출되는 것을 볼 수 있습니다. (부모 클래스꺼는 덮어 씌워져서 호출이 아예 안되었습니다)

이렇게 부모 클래스의 메서드를 가려버리고 본인의 메서드를 호출하게 만드는것이 메서드 오버라이딩 입니다.


3. 추상 클래스 (abstract class)


추상 클래스란 클래스 내부에 구현되지 않은 메서드를 한가지 이상 가지고 있을때, 추상 클래스라 부릅니다.

이 구현되지 않은 메서드는 자식 클래스에서 반드시 구현 해주어야 합니다. 즉, 반드시 재정의(메서드 오버라이딩)하라는 뜻 입니다.

이 추상클래스는 부모 클래스를 설계할때, 자식 클래스에 이 함수는 반드시 있어야한다, 하지만 자식 클래스 각 성격마다 메서드의 내부 구현이 다를것같으니 부모 클래스에서는 해당 메서드를 비워둔다. 하지만 꼭 구현해야하기 때문에 강제한다. 는 뜻 입니다.

추상 클래스를 사용하려면 반드시 abc모듈을 가지고 와야합니다.
abc 모듈 import

또한, 추상클래스는 객체 생성이 불가능합니다. 말그대로 추상적인 모호한 클래스 이기 때문에 이 클래스 자체의 객체 생성은 불가능합니다. (오류남)

추상클래스는 자식 클래스에서 이쁘게 정의하기 위해 정말 메뉴얼?만 만들어 놓은 클래스라고 생각하면 됩니다.

자식 클래스에서도 추상 클래스에서 추상화 해둔 메서드를 재정의 하지 않고, 객체를 생성하려 하면 에러가 발생합니다.


정리하자면, 추상 클래스는 추상 메서드가 하나 이상있는 클래스 이며 해당 클래스는 틀만 만들어 둔거기 때문에 객체 생성이 불가능하다.

추상 클래스를 상속 받은 자식클래스에서도 추상 메서드를 재정의 하지 않는다면 객체 생성이 불가능하다.

추상 클래스를 상속한 자식 클래스에서 추상 메서드를 재정의 다 하면 객체 생성 가능.

이렇게 하는 이유는? 부모 클래스(추상클래스)에서 꼭 만들었으면하는 메서드를 강제하기 위함.

예제로 바로 보겠습니다. 사람이라는 클래스가 있다고 가정해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from abc import *
 
 
class Human(metaclass=ABCMeta):
    # 추상 메서드
    @abstractmethod
    def walk(self):
        print('Human walk : 사람은 걸어요')
 
    # 추상 메서드
    @abstractmethod
    def talk(self):
        pass
 
    # 일반 메서드
    def sleep(self):
        print('Human sleep : 사람은 자요')
 
 
# 자식 클래스 : 어른 클래스
class Adult(Human):
    def walk(self):
        print('Adult walk : 어른은 걷습니다.')
 
 
# 자식 클래스 : 아기 클래스
class Baby(Human):
    # 추상 메서드
    def walk(self):
        print('Baby walk : 아기는 기어갑니다.')
 
    def talk(self):
        print('Baby talk : 아기는 옹알이를 합니다.')
 
 
# 객체 생성
# human = Human() # error
# adult = Adult() # error
baby = Baby()
 
# 재정의한 추상 메서드
baby.walk()
baby.talk()
 
# 부모 클래스 메서드
baby.sleep()
 
cs

  Human 이라는 부모 클래스를 추상 메서드가 2개 walk, talk 가 있는 추상 클래스로 만들었습니다.
추상 메서드메서드 이름 위에 @abstractmethod 라는 것을 붙여야 하고, 추상 클래스에는 (metaclass=ABCMeta)라는 것을 붙여 주어야 합니다. 이것도 추상 클래스를 만들수 있는 클래스를 상속을 받은거죠.

Human 추상 클래스를 상속받은 Baby 클래스와 Adult 클래스를 만들어 보았습니다.

예제의 결과를 보면 Human 클래스를 객체로 만들려고 했지만, 에러가 났고,
추상 메서드를 모두 재정의 하지 않은 Adult 클래스 또한 객체로 만들려 시도할때 에러가 발생하게 됩니다.

오로지 모든 추상 메서드를 구현한 Baby 클래스만 객체를 생성할 수 있었고, 관련 메서드도 호출 할 수 있었습니다.

이렇게 추상 클래스는 틀을 강제 할 수 있는 클래스 입니다.



이렇게 파이썬 클래스에 대해서 2개의 포스팅에 대해서 알아보았습니다. 감사합니다.

지난 포스팅 : 파이썬 클래스 객체 생성자 메서드 [바로가기]

반응형