2023. 3. 7. 12:25ㆍ독서
안녕하세요!
<코딩 자율학습 나도코딩의 파이썬 입문> 멘토를 담당하게 된 이안입니다.
오늘은 9장 '클래스'를 이어서 학습할게요!
오늘 공부할 내용은 다음과 같습니다.
🧐 공부할 내용(p.277~298)
- 클래스 상속하기
- 동작 없이 일단 넘어가기: pass
1. 클래스 상속하기
1) 상속
우리가 현실 세계에서 상속을 받듯, 클래스도 상속이 가능합니다.
다만 조금 다른 게 있다면 현실에서는 부모가 자식에게 상속을 하면 부모에게 있는 것이 자식으로 이동하지만(주로)
클래스는 부모에게도 있고 자식에게도 있답니다.
책의 예시를 살펴보면 다음과 같습니다.
Unit이라는 클래스는 모든 유닛의 부모 클래스입니다.
부모 클래스로부터 나온 자식 클래스로는 지상 유닛이 있을 수 있고, 공중 유닛이 있을 수 있고, 수송 담당 유닛이 있을 수 있습니다.
이런 것처럼 Unit은 모든 Unit이 공통적으로 가져야 하는 부분을 가진 것이라 할 수 있으며, 이를 부모 클래스라고 합니다.
그럼 Unit을 리팩토링 해보죠.
class Unit:
def __init__(self, name:str, hp: int) -> None:
self.name = name
self.hp = hp
기존에 있던 damage와 생성 메시지가 없어졌습니다.
damage와 생성 메시지가 공통사항이 아니었기 때문이죠.
그럼 공격 유닛을 만들어보죠.
class Unit:
def __init__(self, name:str, hp: int) -> None:
self.name = name
self.hp = hp
class AttackUnit(Unit):
def __init__(self, name: str, hp: int, damage: int) -> None:
super().__init__(name, hp) # 부모 클래스 생성자 호출
self.damage = damage
이렇게 생성자 내부에서 부모 클래스 생성자를 호출함으로 인해서 Unit의 인스턴스 변수를 모두 사용할 수 있게 됩니다.
super() 생성자에 대해서는 다음 시간에 좀 더 깊게 알아보도록 할게요.
이제 공격 유닛을 조금 더 구체화시키면 p.280의 코드가 됩니다.
🥕멘토 TIP
상속을 하면 상위 클래스와 하위 클래스가 완벽하게 호완이 되는지 궁금합니다.
이때는 이런 코드를 사용하면 됩니다.
class Unit:
def __init__(self, name:str, hp: int) -> None:
self.name = name
self.hp = hp
class AttackUnit(Unit):
def __init__(self, name: str, hp: int, damage: int) -> None:
super().__init__(name, hp)
self.damage = damage
marine = AttackUnit("Marine", 40, 5)
print(isinstance(marine, Unit))
1. 먼저 마린 즉, 공격 유닛 인스턴스를 하나 생성합니다.
2. 생성된 마린 유닛이 Unit의 인스턴스인지 확인합니다.
isinstance함수는 첫 번째 인자로 확인할 객체를, 두 번째 인자로 확인할 클래스를 입력받습니다.
위 코드에서는 maine이라는 객체가 Unit의 인스턴스이면 즉, class를 통해서 생성된 자원(객체)이면 True를 아니면 False를 반환합니다.
2) 다중 상속
파이썬에선 독특하게 다중 상속이 존재합니다.
책의 예시를 통해 같이 알아봅시다.
공중 유닛 중에는 공격이 가능한 공중 유닛과, 공격이 불가능한 유닛이 있습니다.
이를 나타내기 위해서 AttackUnit과 Flyable을 동시에 상속받는 것입니다.
class Unit:
def __init__(self, name:str, hp: int) -> None:
self.name = name
self.hp = hp
def sayHi(self):
print("Hi")
class Unit2:
def __init__(self, name:str, hp: int) -> None:
self.name = name
self.hp = hp
def sayHi(self):
print("Hello")
class AttackUnit(Unit2, Unit):
def __init__(self, name: str, hp: int, damage: int) -> None:
super().__init__(name, hp)
self.damage = damage
class Flyable:
def __init__(self, flying_speed) -> None:
self.flying_speed = flying_speed
def fly(self,name, location):
print(f"{name} : {location} 방향으로 날아갑니다. [속도 : {self.flying_speed}]")
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name: str, hp: int, damage: int, flying_speed) -> None:
AttackUnit.__init__(self, name, hp, damage)
Flyable.__init__(self, flying_speed)
다중 상속 덕분에 AttackUnit의 특성을 가지면서 동시에 Flyable의 특성도 가질 수 있게 됐어요.
하지만 다중 상속이 언제나 좋은 것은 아닙니다.
다중 상속을 잘못 사용할 경우 다이아몬드 상속 형태가 될 수 있고 이럴 경우 프로그램은 혼란에 빠지게 됩니다.
다중 상속을 지양하면 좋은 이유를 찾아서 공부해 보세요!
🥕멘토 TIP
다이아몬드 상속이란?
다이아몬드 상속은 다중상속에서 주로 나타나는 문제로, 아래와 같은 형태를 이루게 됩니다.
A에 정의된 메서드 foo가 존재합니다.
B와 C는 A를 상속한 클래스입니다. 그리고 B와 C에서 각각 메서드 foo라는 메서드를 가지고 있습니다.
(오버라이딩 여부는 크게 중요하지 않습니다.)
이후 다시 D는 클래스 B와 C를 다중상속 했습니다.
이때 D클래스에서 foo를 실행하면, 어떤 클래스에 있는 메서드가 실행되어야 할까요?
이럴 때는 애매합니다.
파이썬은 자체적으로 먼저 상속된 즉, 상속 괄호에 먼저 적힌 클래스를 우선 탐색하게 됩니다.
class A:
def foo():
pass
class B(A):
def foo():
pass
class C(A):
def foo():
pass
class D(B, C):
pass
D.foo()
이런 코드라면 파이썬은 D의 foo를 호출할 때, 먼저 B에 foo 메서드가 있는지 확인하게 됩니다.
그리고 B에는 foo 메서드가 존재하니 B에 존재하는 foo 메서드를 실행하게 됩니다.
만약 B와 C의 상속 순서가 바뀌었다면 C가 호출 됐겠죠?
하지만 여전히 이는 모호합니다.
이렇기 때문에 현대에 출현한 프로그래밍 언어에서 다중상속을 지원하지 않는 경우가 대부분이고,
파이썬에서 역시 다중상속은 지양해야 할 점으로 꼽히기도 합니다.
3) 메서드 오버라이딩
메서드 오버라이딩은 메서드를 재정의하는 것입니다.
즉, 부모 클래스에 있는 메서드를 이름은 그대로 사용하되 내용만 바꿔서 사용하는 것이죠.
책의 move가 대표적이 예시입니다.
한번 같이 작성해 볼까요?
class Unit:
def __init__(self, name:str, hp: int, speed: int) -> None:
self.name = name
self.hp = hp
self.speed = speed
def move(self, location):
print("[지상 유닛 이동]")
print(f"{self.name} : {location} 방향으로 이동합니다. [속도 {self.speed}]")
class AttackUnit(Unit):
def __init__(self, name: str, hp: int, damage: int, speed: int) -> None:
super().__init__(name, hp, speed)
self.damage = damage
class Flyable:
def __init__(self, flying_speed) -> None:
self.flying_speed = flying_speed
def fly(self,name, location):
print(f"{name} : {location} 방향으로 날아갑니다. [속도 : {self.flying_speed}]")
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name: str, hp: int, damage: int, flying_speed: int) -> None:
AttackUnit.__init__(self, name, hp, damage, 0)
Flyable.__init__(self, flying_speed)
def move(self, location):
print("[공중 유닛 이동]")
self.fly(self.name, location)
Unit 클래스에서 move 메서드를 생성했습니다. 그리고 FlyableAttackUnit에도 move 메서드가 있죠?
이 경우 메서드 오버라이딩에 해당합니다.
이름만 같고, 실상은 다른 일을 하는 것이죠.
간단하죠? 이렇게 상속 구조를 사용하실 때는 오버라이딩을 할 수 있는 방향으로 코드 설계를 하는 것이 좋습니다.
유연한 개발이 가능하니까요!
2. 동작 없이 일단 넘어가기: pass
파이썬에서는 특이하게 pass라는 기능이 있어요.
'아무것도 하지 않고 일단 그냥 넘어간다'라는 의미이죠.
p.293 책의 예시를 한번 볼까요?
def game_start():
print("[알림] 새로운 게임을 시작합니다.")
def game_over():
pass
game_start()
game_over()
이 코드를 실행했을 때 game_start()에 대한 출력만 나오게 됩니다.
그럼 이런 생각이 드실 수도 있습니다.
그냥 주석처리하고 실행해도 되지 않나?
맞습니다.
하지만 주석을 사용하지 않고도 더 가독성 좋은 코드를 작성할 수 있을 뿐만 아니라,
이 기능은 완성해야 한다는 것을 명시적으로 보여주는 역할도 하죠.
그렇기 때문에 적절한 pass 사용은 자연스러운 코딩 흐름을 유지하는데 도움을 줍니다!
이번 시간에는 메서드 오버라이딩과 pass에 대해 알아봤어요.
클래스와 상속이라는 개념이 조금 어렵게 다가올 수 있을 것 같아요.
공부하시다 궁금한 점이 있다면 댓글 남겨주세요!
✅ 정리
* 클래스 상속하기
* 상속
* 다중 상속
* 메서드 오버라이딩
* pass
* 코딩의 흐름을 유지하는데 좋음
'독서' 카테고리의 다른 글
(나도코딩의 파이썬 입문) 10장. 예외 처리(p.326~337) (0) | 2023.03.10 |
---|---|
(나도코딩의 파이썬 입문) 9장. 클래스(p.295~320) (0) | 2023.03.10 |
(나도코딩의 파이썬 입문) 9장. 클래스(p.257 ~ p.276) (0) | 2023.03.06 |
(나도코딩의 파이썬 입문) 8장. 입출력(p.238 ~ p.250) (0) | 2023.03.06 |
(나도코딩의 파이썬 입문) 8장. 입출력(p.221 ~ p.237) (0) | 2023.03.05 |