Class

Class란?

클래스: 객체를 만들기 위한 설계

  • 속성: class를 설명하는 특징을 나타내는 것
  • 메서드: 어떠한 동작 (함수) 객체(인스턴스): 설계도로부터 만들어낸 제품
  • 객체는 인스턴스와 동일한 것
  • 인스턴스: class랑 연관지어 표현할 때 사용 ex) B 객체, A라는 Class에서 만들어진 B라는 인스턴스

Class의 기본 구조

# class 기본 구조 
class 클래스이름:
    def 메서드이름:
        명령블록 

# 호출하기 
인스턴스 = 클래스이름()
인스턴스.메서드()
class Monster:
    def say(self):
        print('나는 몬스터다')

goblin = Monster()
goblin.say()

결과: 나는 몬스터다

생성자(init) 함수

init 메서드는 인스턴스를 만들 때 반드시 호출되는 메서드이다. 가장 먼저 호출되는 함수라고 해서 init 이다. 그러면 언제 인스턴스가 생성이 되는가? 예로 들면 goblin = Monster() 시점이다. 인스턴스가 만들어질 때 괄호 안에 적는 값들은 init 의 매개 변수(인자)로 전달된다.

class Monster:
    def __init__(self, health, attack, speed):
        self.health = health
        self.attack = attack
        self.speed = speed
    def decreae_health(self, num):
        self.health -= num
    def get_health(self):
        return self.health

# goblin 인스턴스 생성
goblin = Monster(800, 120, 300)
goblin.decreae_health(100)
goblin.get_health()

# wolf 인스턴스 생성
wolf = Monster(400, 250, 150)

결과: 700

여기서 self는 매개 변수로 치지 않는다. self는 인스턴스 자기자신을 의미한다. 아래와 같은 예시가 있을 때 goblin self.health 를 했을 때 goblin의 self.health는 800이 되고, wolf 인스턴스의 self.health는 1500이 된다.

self.health는 현재 호출되고 있는 인스턴스의 health 변수에 인스턴스를 호출할 때 받았던 800이라는 값을 저장하라는 의미이다.


상속

상속은 부모클래스의 속성이나 메서드를 자식클래스로 그대로 가져오는 것을 의미한다.

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('늑대', 200, 300)
wolf.move()
shark = Shark('상어', 500, 600)
shark.move()
dragon = Dragon('드래곤', 700, 500)
dragon.move()

결과
늑대 지상에서 이동
상어 헤엄쳐서 이동
드래곤 날아서 이동

pass는 부모클래스에서 상속받은 메서드만 사용할 때 사용한다. 부모클래스에서 정의했던 메서드를 재정의 할 때는 같은 이름의 메서드를 적으면 된다. 이를 메서드 오버라이딩이라고 한다.

wolf 인스턴스를 만들때는 Monster class 의 init 함수가 호출된다. wolf 인스턴스는 move 함수를 사용할 때 Monster class의 move 함수를 쓰지만 메서드 오버라이딩을 한 shark와 dragon 인스턴스는 각각 Shark class, Dragon class의 move 함수를 사용한다.

참고로 부모 클래스에서 상속받은 파라미터들을 모두 사용하지 않으면 아래와 같은 에러가 발생한다.

>>> wolf = Wolf('늑대')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 2 required positional arguments: 'health' and 'attack'

상속할 때 기본값 사용하기

클래스를 상속할 때 기본적으로 값을 할당해 줄 수도 있다.

class Monster:
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack

    def move(self):
        print(f'{self.name} 지상에서 이동, 체력: {self.health}, 공격력: {self.attack}')

class Wolf(Monster):
    def __init__(self, name):
        super().__init__(name, health=200, attack=300)


wolf = Wolf('늑대')
wolf.move()

위의 예시처럼 Wolf 자식 클래스에서 __init__을 해줄 때 부모 클래스의 __init__() 의 변수에 기본값을 할당해주면, 인스턴스를 만들 때 기본값이 할당되지 않은 변수들만 파라미터로 써주면 된다.

>>> wolf = Wolf('늑대')
>>> wolf.move()
늑대 지상에서 이동, 체력: 200, 공격력: 300

이렇게 인스턴스를 만들 때 name만 파라미터로 넣어줘도 정상적으로 작동하는 것을 볼 수 있다. 또한, 기본값은 자식 클래스의 __init__ 함수안에 작성해주어도 동일한 결과를 얻을 수 있다. 보통은 부모 클래스에 기본값을 설정하는 것보다 자식 클래스에 기본값을 설정하는 것 같다. 그렇지 않으면 부모 클래스가 따로 존재할 이유가 없기 떄문이다.

class Monster:
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack

    def move(self):
        print(f'{self.name} 지상에서 이동, 체력: {self.health}, 공격력: {self.attack}')

class Wolf(Monster):
    def __init__(self, name, health=200, attack=300):
        super().__init__(name, health, attack)


wolf = Wolf('늑대')
wolf.move()
>>> wolf = Wolf('늑대')
>>> wolf.move()
늑대 지상에서 이동, 체력: 200, 공격력: 300

기본값이 지정되어 있더라고 새로운 값을 넣어주면 새로운 값으로 대체된다.

>>> wolf = Wolf('늑대', 500, 600)
>>> wolf.move()
늑대 지상에서 이동, 체력: 500, 공격력: 600

메서드 오버라이딩

이번에는 생성자 자체를 오버라이딩해서 사용해보겠다. 위의 예시에서 dragon에게만 스킬을 추가하기 위해 wolf, shark class 에서 사용되지 않는 변수들을 추가하고 싶다.

class Dragon(Monster):
    def __init__(self, name, health, attack, skills):
        self.name = name
        self.health = health
        self.attack = attack
        self.skill = skills

만약 dragon class에 skill을 추가한다면 위와 같이 적어도 되지만 변수가 많아질 수록 코드는 복잡해진다. 이때 사용하는 것이 super()이다. 아래처럼 super()로 부모클래스를 불러올 수 있고, super().init()은 부모 클래스의 init 함수를 불러오는 것이다.

class Dragon(Monster):
    def __init__(self, name, health, attack, skills):
        super().__init__(name, health, attack)
        self.skills = skills

이제 인스턴스를 만들어 보자

class Dragon(Monster):
    def __init__(self, name, health, attack, skills):
        super().__init__(name, health, attack)
        self.skills = skills
        
dragon = Dragon('드래곤', 700, 500, ('불공격','꼬리공격','날개공격'))

이처럼 skill 변수에 값을 할당하면 되지만, 인스턴스를 만들 때 마다 계속 (‘불공격’,’꼬리공격’,’날개공격’)라는 인자를 넣어줘야 한다. 하지만 이 인자가 계속 같다면 아래와 같이 class에서 할당해주면 편하다. 할당해주면 더 이상 skill이라는 변수를 따로 받을 필요가 없어진다.

class Dragon(Monster):
    def __init__(self, name, health, attack):
        super().__init__(name, health, attack)
        self.skills = ('불공격','꼬리공격','날개공격')

이제 새로운 메서드를 만들고 인스턴스를 만들어본다.

import random

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)]}')

dragon = Dragon('드래곤', 700, 500)
dragon.skill()

결과
드래곤 스킬 사용: 날개공격

클래스 변수

클래스 변수는 인스턴스들이 모두 공유하는 변수이다. 클래스 변수는 클래스 자체의 변수이기 때문에 self를 쓰지 않는다.

class Monster:
    max_num = 1000  # 최대 몬스터 개수
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack
        Monster.max_num -= 1  # 인스턴스가 만들어질 때마다 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)]}')

print(Monster.max_num)  # 1000

wolf = Wolf('늑대', 200, 300)
print(wolf.max_num)  # 999
print(Monster.max_num)  # 999

shark = Shark('상어', 500, 600)
print(shark.max_num)  # 998
print(wolf.max_num)  # 998
print(Monster.max_num)  # 998

dragon = Dragon('드래곤', 700, 500)
print(dragon.max_num)  # 997
print(shark.max_num)  # 997
print(wolf.max_num)  # 997
print(Monster.max_num)  # 997

보이는 것과 같이 max_num이라는 클래스 변수는 모든 인스턴스들이 공유하기 때문에 어떤한 인스턴스의 max_num을 찍어도 같은 값이 나오는 것을 볼 수 있다.