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을 찍어도 같은 값이 나오는 것을 볼 수 있다.