[Python] 클래스
컴퓨터 사이언스 부트캠프 with 파이썬을 보고 정리한 내용입니다.
클래스 관계
IS-A: 상속
- ‘~은 ~의 한 종류다’ 라는 뜻 ex. A laptop IS A computer
- 한 객체가 다른 객체의 모든 특성(멤버)와 기능(메서드)를 가진 상태에서 그 외의 다른 특성이나 기능을 갖도록 만들고 싶다면
- 상속을 하는 클래스를 기본 클래스/부모 클래스/슈퍼 클래스라고 함
- 상속을 받는 클래스를 파생 클래스/자식 클래스/서브 클래스라고 함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#기본 클래스인 Computer 클래스
class Computer:
def __init__(self, cpu, ram):
self.CPU = cpu
self.RAM = ram
def browse(self):
print('browse')
def work(self):
print('work')
class Laptop(Computer): #Computer를 상속
def __init__(self, cpu, ram, battery):
super().__init__(cpu, ram) #슈퍼클래스인 Computer의 생성자를 이용해 cpu, ram 초기화
self.battery = battery
def move(self, to):
print('move to {}'.format(to))
#따로 지정하지 않아도 browse(), work() 메서드는 이미 가지고 있음
HAS-A: 합성/통합
- ‘~이 ~을 가진다, 포함한다’ ex. A Computer HAS A CPU
합성
1
2
3
4
5
6
7
8
9
10
class CPU:
pass
class RAM:
pass
class Computer:
def __init__(self):
self.cpu = CPU()
self.ram = RAM()
- Computer 객체가 생성될 때 CPU 객체도 같이 만들어졌다가 Computer 객체가 사라질 때 CPU 객체도 함께 사라짐
- 객체의 생명주기가 같고 컴퓨터가 CPU를 소유하고 있는 모양새일 때
통합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Gun:
def __init__(self,kind):
self.kind = kind
def bang(self):
print('bang bang!')
class Police:
def __init__(self):
self.gun = None
def acquire_gun(self, gone):
self.gun = gun
def relase_gun(self)
gun = self.gun
self.gun = None
return gun
def shoot(self):
if self.gun:
self.gun.bang()
else:
print('Unable to shoot')
- Police 객체는 만들어질 때 아직 Gun 객체를 가지고 있지 않음, 이후 acquire_gun() 메서드를 통해 Gun 객체를 멤버로 가지게 됨, 또 release_gun()을 통해 가지고 있던 총을 반납 가능
- 두 객체는 생명주기를 함께하지 않는 상대적으로 약한 관계
메서드 오버라이딩과 다형성
메서드 오버라이딩
- 파생 클래스 안에서 상속받은 메서드를 다시 구현하는 것
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CarOwner:
def __init__(self, name):
self.name = name
def concentrate(self):
print('{} can not do anything else'.format(self.name))
#나머지 메서드...
class Car:
def __init(self, owner_name):
self.owner = CarOwner(owner_name)
def drive(self):
self.owner.concentrate()
print('{} is driving now.'.format(self.owner.name))
#나머지 메서드...
class SelfDrivingCar(Car): #Car 클래스 상속
def drvie(self):
print('Car is driving by itself')
- 슈퍼클래스인 Car 의 경우 drive() 를 호출하자마자 차 주인의 concentrate()을 호출하여 차 주인이 다른 걸 하면 안 된다는 말을 출력함
- 그러나 Car를 상속받은 SelfDrivingCar의 경우 같은 drive() 를 다시 정의해서(→ 메서드 오버라이딩), 차가 자율주행을 하고 있다는 말을 출력함
→ 이렇게 같은 이름의 메서드를 호출해도 호출한 객체에 따라 다른 결과를 내는 것을 ‘다형성’(polymorphism)이라고 함
다형성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal:
def eat(self):
print('eat something')
class Lion(Animal):
def eat(self):
print('eat meat')
class Deer(Animal):
def eat(self):
print('eat grass')
class Human(Animal):
def eat(self):
print('eat meat and grass')
- 모든 파생 클래스가 공통으로 가질 메서드인 eat()은 기본 클래스에 두고,
- 이 기본 클래스를 상속한 파생 클래스들의 식성에 따라 eat() 을 다시 정의함
- 만약 Animal 클래스의 인스턴스는 못 만들게 하고 싶다면?
- Animal 클래스를 추상 클래스로 만들면 됨
- 추상 클래스
- 독자적으로 인스턴스를 만들 수 없고 함수의 몸체가 없는 추상 메서드를 하나 이상 가지고 있어야 함
- 추상 클래스를 상속받는 파생 클래스는 추상 클래스를 반드시 오버라이딩 해야 함(그렇지 않으면 파생 클래스도 추상 클래스가 되어 인스턴스를 만들 수 없음)
1
2
3
4
5
6
7
8
from abc import *
#abc 는 abstract base class의 약자
class Animal(metaclass = ABCMeta):
@abstractmethod
def eat(self):
pass
#이렇게 데코레이터 + 메서드 구현부를 비워놓으면 eat()은 추상 메서드가 됨
클래스 설계 예제
클래스 설계 시에는 2가지를 고려
- 공통 부분을 기본 클래스로 묶어 코드를 재사용할 수 있도록 한다.
- 부모가 추상 클래스인 경우를 제외하고, 파생 클래스에서 기본 클래스의 여러 메서드를 오버라이딩한다면 파생 클래스는 만들지 않는 것이 좋다.
Character 클래스 만들기 (추상 클래스)
- 게임 캐릭터에는 몬스터와 플레이어가 있다.
- 모든 캐릭터(추상 클래스)는 다음과 같은 특성을 가짐
- 인스턴스 멤버: 이름, 체력, 공격력을 가짐
- 인스턴스 메서드: 공격할 수 있고 공격당하면 피해를 입음(모두 추상 메서드로 구현)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from abc import *
#추상클래스
class Character(metaclass = ABCMeta):
def __init__(self, name, hp, power):
self.name = name
self.HP = hp
self.power = power
#추상메서드
@abstractmethod
def attack(self, other, attack_kind):
pass
@abstractmethod
def get_damamge(slef.power, attack_kind):
pass
def __str__(self):
return '{} : {}'.format(slef.name, self.HP)
Player 클래스 만들기
- 플레이어의 특성
- 추가되는 멤버: 다양한 공격 목록을 담을 수 있는 기술 목록
- attack: 공격 종류가 기술 목록 안에 있다면 상대 몬스터에게 피해를 입힐 수 있음
- get_damage: 플레이어가 피해를 입을 때 몬스터의 공격 종류가 플레이어의 기술 목록에 있다면 몬스터의 공격력이 반감되어 hp가 공격력의 반절만 깎임
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Player(Character): #상속
def __init__(slef, name = 'player', hp =100, power = 10, *attack_kinds):
super().__init__(name, hp, power)
self.skills = []
for attack_kind in attack_kinds:
slef.skills.append(attack_kind)
#추상 메서드는 반드시 재정의해야 함
def attack(self, other, attack_kind):
if attack_kind in self.skills:
other.get_damage(self.power, attack_kind)
def get_damage(self, power, attack_kind):
if attack_kind in slef.skills:
self.HP -= (power//2)
else:
self.HP -= power
Monster 클래스 만들기
- 몬스터의 특성
- 불 몬스터와 얼음 몬스터가 있음
- 추가되는 멤버: 공격 종류를 가진다(불 몬스터는 FIRE, 얼음 몬스터는 ICE)
- 공통 메서드(두 몬스터는 같은 행동을 함)
- 공격 종류가 몬스터의 속성과 같다면 공격
- 자신과 속성이 같은 공격을 당하면 오히려 체력이 증가하고, 그렇지 않으면 감소 (공격력만큼)
- 서로 다른 메서드
- 불 몬스터는 얼음 몬스터는 하지 않는 특별한 행동을 한다(fireball)
→ fireball을 제외한 나머지 메서드도 겹치고 추가되는 멤버도 겹치므로 기본 Monster 클래스를 만들고 이를 각각 상속하는 것이 좋을 것 같다.
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
class Monster(Character):
def __init__(self, name, hp, power):
super().__init__(name, hp, power)
self.attack_kind = 'None'
def attack(self, other, attack_kind):
if self.attack_kind == attack_kind:
other.get_damage(self.power, attack_kind)
def get_damage(self, power, attack_kind):
if self.attack_kind == attack_kind:
self.HP += power
else:
self.HP -= power
def get_attack_kind(self):
return self.attack_kind
class IceMonster(Monster):
def __init__(self, name = 'Ice monster', hp = 50, power = 10):
super().__init__(name, hp, power)
self.attack_kind = 'ICE'
class FireMonster(Monster):
def __init__(self, name = 'Fire monster', hp = 50, power = 10):
super().__init__(name, hp, power)
self.attack_kind = 'FIRE'
#추가 메서드
def fireball(self):
print('fireball')
연산자 오버로딩
- 연산자 오버로딩(operator overloading)이란 클래스 안에서 메서드로 연산자를 새롭게 구현하는 것 (다형성의 특별한 형태)
- 다른 객체나 일반적인 피연산자와 연산을 할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point:
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y
def set_point(self, x, y):
self.x = x
self.y = y
def get_point(self):
return self.x, self.y
def __str__(self):
return'({x}, {y})'.format(x=self.x, y=self.y)
- x좌표, y좌표에 숫자를 더해서 새로운 좌표객체를 만들고 싶다.
1
2
3
4
5
if __name__ == "__main__":
p1 = Point(2,2)
p2 = p1 + 3
print(p2)
- 이렇게 하면 Point 객체와 int 객체는 더할 수 없다고 오류가 남
- 다음과 같이 연산자 오버로딩을 써서 더하기를 다시 정의해주면 정상적으로 동작
1
2
3
4
5
#(class 정의 내에)
def __add__(self, n):
x = self.x + n
y = self.y + n
return Point(x,y)
- 하지만 3+p1 하면 똑같이 오류가 남, 순서 무관 덧셈을 할 수 있으려면 다른 연산자도 오버로딩 필요:
__radd__
- 산술 연산자 오버로딩 메서드
__add__
__sub__
__mul__
__truediv__
__floordiv__
This post is licensed under CC BY 4.0 by the author.