[Python 독학] 클래스 ②
생성자 (Constructor)
: 인스턴스를 만들 때 호줓되는 매서드
객체에 초깃값을 설정해야 할 필요가 있을 때는
메서드를 호출하여 초깃값을 설정하기보다
생성자를 구현하는 것이 안전하다.
속성 추가
# Monster 클래스에 속성(체력, 공격력, 이동속도) 추가하기
class Monster:
def __init__(self, health, attack, speed):
self.health = health
self.attack = attack
self.speed = speed
goblin = Monster(800, 120, 300)
wolf = Monster(1500, 200, 350)
__init__ : 인스턴스를 만들 때 반드시 호출되는 메서드로 가장 먼저 호출된다.
또한 self는 매게변수로 취급되지 않기 때문에 하단에 적힌
goblin = Monster(800, 120, 300)
wolf = Monster(1500, 200, 350)
에서 goblin의 800 → health, 120 → attack, 300 → speed,
wolf의 1500 → health, 200 → attack, 350 → speed로
연결이 된다. 대신 self는 인스턴스 자기 자신을 뜻한다.
즉, 여기서는 self가 goblin 인스턴스 자기 자신,
wolf 인스턴스 자기 자신이 되어
self.health = health
self.attack = attack
self.speed = speed
여기서 goblin의 health 자리에 우리가 적어넣은 800이
들어가고, attack 자리에 120이 들어가 저장되는 것이다.
wolf도 마찬가지.
조금 더 응용해서
체력을 감소하는 메서드(1)와
체력을 가져오는 메서드(2)를
만들면 다음과 같다.
# : 인스턴스를 만들 때 호출되는 메서드
class Monster:
def __init__(self, health, attack, speed):
self.health = health
self.attack = attack
self.speed = speed
def decrease_health(self, num):
self.health -= num
def get_health(self):
return self.health
# 고블린 인스턴스 생성
goblin = Monster(800, 120, 300)
goblin.decrease_helath(100)
print(goblin.get_health())
상속 (Ingeritance)
class 클래스이름(상속할 클래스이름)
상속받은 클래스(자식 클래스)는
상속해준 클래스(부모 클래스)의
속성과 메서드를 사용할 수 있다.
예를 들어 게임을 만들 때,
몬스터(부모 클래스)를 만들었다고 하자.
몬스터를 속성에 따라 땅/물/공중 몬스터로
(자식 클래스)나누어 만들었고, 그에 따라
공격 기술을 넣어주고자 한다.
이때 '몬스터'의 종류 별로 처음부터 다 코딩을
짜주는 것이 아니라 각자의 기술이나 필살기를
제외하고는 몬스터라는 큰 묶음으로 묶인
(공통)기본 성질과 메서드는 겹치니까 그대로
상속 시켜주면 되는 것이다.
그래서 클래스들에 중독된 코드를 제거하고
유지보수를 편하게 하기 위해 주로 사용된다.
# 부모클래스 정의
class Monster:
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
def move(self):
print("지상에서 이동하기")
#자식클래스 정의
class Wolf(Monster):
pass
class Shark(Monster)
def move(self):
print("헤엄치기")
class Dragon(Monster):
def move(self):
print("날기")
참고로 __init__이 생략 가능한 이유는
전부 Monster 클래스를 상속받아서
생성자가 자동으로 호출되기 때문이다.
★pass
Wolf 밑에 pass는 별도의 속성 추가 없이
Monster에서 상속받은 메서드만
쓰겠다는 뜻이다. 코드 블록 부분을 구현하지
않고 잠시 넘어가고 싶을 때 주로 사용한다.
★메서드 오버라이딩 (Overriding)
부모에서 정의했던 메서드를 다시 재정의하는 것이다.
'지상에서 이동하기'를 '헤엄치기'와 '날기'로
재정의해준 부분을 메서드 오버라이딩이라고 볼 수 있다.
▼ 적용 예시
# 부모클래스 정의
class Monster:
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
def move(self):
print(f"[{self.name}] 지상에서 이동하기")
#자식클래스 정의
class Wolf(Monster):
pass
class Shark(Monster):
def move(self): # 메서드 오버라이딩
print(f"[{self.name}] 헤엄치기")
class Dragon(Monster):
def move(self): # 메서드 오버라이딩
print(f"[{self.name}] 날기")
wolf = Wolf("울프", 1500, 200)
wolf.move()
shark = Shark("샤크", 3000, 400)
shark.move()
dragon = Dragon("드래곤", 8000, 800)
dragon.move()
생성자 오버라이딩
드래곤에게만 스킬 3가지를 추가하고
그 스킬이 랜덤으로 나오게 해줘라
라는 미션이 주어졌을 때는
어떡하면 되는지는 다음과 같다.
# 한국어 유니코드 인코딩
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
# 모듈 가져오기
import random
# 부모클래스 정의
class Monster:
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
def move(self):
print(f"[{self.name}] 지상에서 이동하기")
#자식클래스 정의
class Wolf(Monster):
pass
class Shark(Monster):
def move(self): # 메서드 오버라이딩
print(f"[{self.name}] 헤엄치기")
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
# 메서드 오버라이딩
def move(self):
print(f"[{self.name}] 날기")
def skill(self):
print(f"[{self.name}] 스킬 사용 {self.skills[random.randint(0,2)]}")
wolf = Wolf("울프", 1500, 200)
wolf.move()
shark = Shark("샤크", 3000, 400)
shark.move()
dragon = Dragon("드래곤", 8000, 800)
dragon.move()
dragon.skill()
사실 인강에서 선생님은
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
이 한국어 유니코드 인코딩을 사용하지
않았지만 난 이게 없으니 VSC가 돌아가질 않고
계속 에러를 띄워서 어쩔 수 없이 앞부분에 넣었다.
우선 드래곤에게만 스킬을 부여할거라
Monster 클래스의 변수에 skill을
추가하는 건 별로 좋지 않다.
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack, skill):
self.name = name
self.health = health
self.attack = attack
…
그래서 드래곤 클래스에
생성자 오버라이딩 하는 것이 더 좋다.
self.name = name
self.health = health
self.attack = attack
…
근데 만약 변수가 지금은 4~5개지만
실제 게임에서는 수십, 수백가지가 될 수 있다.
이때 이걸 일일이 이런 식으로 친 수는 없다.
그래서 부모 클래스의 변수를 전부 그대로
가져올 때 사용하는 것이 바로
super().__init__(name, health, attack)
이다. super()로 부모 클래스를 불러올 수 있고,
이 뒤에 __init__ 불러와서 호출할 수 있다.
(괄호) 안에는 넘겨받은 self를 제외한 부모 클래스에서
가져올 변수들을 그대로 써주는 것이다.
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack, skill):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
그러면 최종적으로 Dragon 클래스는
이렇게 완성된다. name, health, attack은
Monster 클래스에서 그대로 가져오고,
드래곤에만 지정한 skill 변수만 위와 같이 넣어준다.
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack, skill)
self.skill = skill
…(중략)
dragon = Dragon("드래곤", 8000, 800, ("불뿜기", "꼬리치기", "날개치기"))
dragon.move()
사실 self.skill = skill 하고 하단에 드래곤에게
부여할 값에서 스킬을 부여해도 무관하다.
스킬은 변할 일이 없기 때문에 튜플로 묶어줬다.
하지만 체력이나 공격력은 조정될 수 있는
값이고, 스킬들은 고정되어 있는 값이라
수정이 필요한 경우 매번 새로 만들어
중복되는 상황이 생긴다.
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
이걸 예방하기 위해 튜플로 묶은 스킬들을
속성 자체에 직접적으로 대입을 해준다.
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
# 메서드 오버라이딩
def move(self):
print(f"[{self.name}] 날기")
def skill(self):
print(f"[{self.name}] 스킬 사용 {self.skills[random.randint(0,2)]}")
여기에 이제 두번째 미션이었던
랜덤으로 스킬 나오기를 지정하기 위해
다음과 같이 메서드 오버라이딩을 해주고
코드 맨 위에 import random을 써준 후
하단에 dragon값을 부여해준 부분에
skill을 내보낼 수 있도록 dragon.skill()
써준다. 그렇게 해서 완성된 코드가👇🏻
# 한국어 유니코드 인코딩
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
# 모듈 가져오기
import random
# 부모클래스 정의
class Monster:
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
def move(self):
print(f"[{self.name}] 지상에서 이동하기")
#자식클래스 정의
class Wolf(Monster):
pass
class Shark(Monster):
def move(self): # 메서드 오버라이딩
print(f"[{self.name}] 헤엄치기")
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
# 메서드 오버라이딩
def move(self):
print(f"[{self.name}] 날기")
def skill(self):
print(f"[{self.name}] 스킬 사용 {self.skills[random.randint(0,2)]}")
wolf = Wolf("울프", 1500, 200)
wolf.move()
shark = Shark("샤크", 3000, 400)
shark.move()
dragon = Dragon("드래곤", 8000, 800)
dragon.move()
dragon.skill()
클래스 변수
쉽게 말해 인스턴스들이 모두
공유하는 변수를 말한다.
몬스터가 최대 1000마리까지만
나타날 수 있도록 제한을 걸 경우.
wolf, shark, dragon 모두 몬스터라
셋 중 누구 하나가 생성되고
1000에서 1씩 깎여야 한다.
class Monster:
max_num = 1000
def __init__(self, name, health, attack):
self.name = name
…
Monster클래스 아래에 최대 갯수를
max_num = 1000을 지정해준다.
class Monster:
max_num = 1000
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
Monster.max_num -= 1
…
__init__ 을 지날 때마다 몬스터가 한마리씩
생성되므로 max_num에서 1씩 빼줄것이다.
이때, 클래스(Monster) 변수를 쓸때는
self를 쓰지 않고, 클래스 자체의 변수라고
해서 바로 클래스.변수이름 형식으로 쓴다.
→ Monster.max_num
제대로 작동하는지 확인하기 위해
wolf = Wolf("울프", 1500, 200)
wolf.move()
print(wolf.max_num)
shark = Shark("샤크", 3000, 400)
shark.move()
print(shark.max_num)
dragon = Dragon("드래곤", 8000, 800)
dragon.move()
dragon.skill()
print(dragon.max_num)
하단에
print(wolf/shark/dragon.max_num) 을
출력해주면
이렇게 1씩 줄어드는 것을
확인 할 수 있다!
끝!!
최종 코드▼
# 한국어 유니코드 인코딩
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
# 모듈 가져오기
import random
# 부모클래스 정의
class Monster:
max_num = 1000
def __init__(self, name, health, attack):
self.name = name
self.health = health
self.attack = attack
Monster.max_num -= 1
def move(self):
print(f"[{self.name}] 지상에서 이동하기")
#자식클래스 정의
class Wolf(Monster):
pass
class Shark(Monster):
def move(self): # 메서드 오버라이딩
print(f"[{self.name}] 헤엄치기")
class Dragon(Monster):
# 생성자 오버라이딩
def __init__(self, name, health, attack):
super().__init__(name, health, attack)
self.skills = ("불뿜기", "꼬리치기", "날개치기")
# 메서드 오버라이딩
def move(self):
print(f"[{self.name}] 날기")
def skill(self):
print(f"[{self.name}] 스킬 사용 {self.skills[random.randint(0,2)]}")
wolf = Wolf("울프", 1500, 200)
wolf.move()
print(wolf.max_num)
shark = Shark("샤크", 3000, 400)
shark.move()
print(shark.max_num)
dragon = Dragon("드래곤", 8000, 800)
dragon.move()
dragon.skill()
print(dragon.max_num)
실습문제
아이템 공통 : 이름, 가격, 무게, 판매하기, 버리기
장비 아이템 : 착용효과, 착용하기
소모품 아이템 : 사용효과, 사용하기
(단, 버리기는 버릴 수 있는 아이템만 가능하다)
# 클래스 생성
class Item():
def __init__(self, name, price, weight, isdropable):
self.name = name
self.price = price
self.weight = weight
self.isdropable = isdropable
def sale(self):
print(f"[{self.name}] 판매가격은 [{self.price}]")
def discard(self):
if self.isdropable:
print(f"[{self.name}] 버렸습니다.")
else:
print(f"[{self.name}] 버릴 수 없습니다.")
class WearableItem(Item):
def __init__(self, name, price, weight, isdropable, effect):
super().__init__(name, price, weight, isdropable)
self.effect = effect
def wear(self):
print(f"[{self.name}] 착용했습니다. {self.effect}")
class UsableItem(Item):
def __init__(self, name, price, weight, isdropable, effect):
super().__init__(name, price, weight, isdropable)
self.effect = effect
def use(self):
print(f"[{self.name}] 사용했습니다. {self.effect}가 지속됩니다.")
# 인스턴스 생성
sword = WearableItem("이가닌자의검", 30000, 3.5, True, "체력 5000증가, 마력 5000증가")
sword.wear()
sword.sale()
sword.discard()
potion = UsableItem("신비한투명물약", 150000, 0.1, False, "투명효과 300초 지속")
potion.discard()
potion.sale()
potion.use()