Python

[Python 독학] 클래스 ②

みずき 2021. 7. 7. 15:33

생성자 (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()