나도코딩 파이썬 9장 - 클래스
9.1 게임 소개
~ 게임 생략!
실제 게임에서는 서로 다른 종류의 유닛들이 최소 수십 개에서 수백 개까지 존재한다. 또한, 유닛마다 서로 다른 정보(이름, 체력, 공격력 등)가 있는데, 새로운 유닛을 만들 때마다 코드를 일일이 짜는 방법으로 관리하는 것은 무리이다.
9.2 클래스와 객체 생성하기
클래스 = 붕어빵 틀
[클래스의 기본 형식]
class 클래스명 :
def 메서드명1(self, 전달값1, 전달값2, ...)
실행할 명령1
실행할 명령2
...
def 메서드명2(self, 전달값1, 전달값2, ...)
실행할 명령1
실행할 명령2
...
- 클래스명은 일반적으로 하나 또는 여러 단어의 조합으로 만드는데, 각 단어의 첫 글자는 대문자로 작성한다.
- 클래스 안에는 필요한 함수를 정의하는데, 클래스 안에 정의하는 함수를 특별히 메서드(method)라고 한다.
- 메서드의 첫 번째 전달값 위치에는 self를 넣는다.
- 클래스의 메서드 안에 정의한 변수를 인스턴스 변수라고 한다. 형식 : self.변수명 = 값
class Unit:
def __init__(self, name, hp, damage):
self.name = name
self.hp = hp
self.damage = damage
print("{0}유닛을 생성했습니다.".format(self.name))
print("체력 {0}, 공격력 {1}".format(self.hp, self.damage))
처음으로 클래스를 만들었다. 그런데 클래스도 함수와 마찬가지로 정의만 해서는 아무런 동작도 하지 않는다. 지금은 붕어빵 틀만 불에 올려 달군 상태이다.
그럼 클래스를 사용해 유닛을 직접 만들어보자.
형식 : 객체명 = 클래스명(전달값1, 전달값2, ..)
소괄호 안에는 클래스의 __init__() 메서드에 정의한 부분 중 self를 제외한 나머지 전달값을 넣는다.
soldier1 = Unit("보병", 40, 5)
soldier2 = Unit("보병", 40, 5)
tank = Unit("탱크", 150, 35)
'''
보병유닛을 생성했습니다.
체력 40, 공격력 5
보병유닛을 생성했습니다.
체력 40, 공격력 5
탱크유닛을 생성했습니다.
체력 150, 공격력 35
'''
코드를 작성하고 실행하면 유닛들이 생성된다. 클래스 하나로 서로 다른 유닛 3개를 만들었는데, 이렇게 만들어진 유닛들을 객체라고 한다. soldier1, soldier2, tank는 객체이고 클래스의 인스턴스이다. 붕어빵도 객체이고, 붕어빵 틀의 인스턴스이다. 객체와 인스턴스는 사실 같은 개념이다. 보통 객체를 단독으로 부를 때는 객체로, 클래스와 연결지어 부를 때는 인스턴스로 표현한다.
9.2.1 생성자: __init__()
Unit 클래스에 __init__() 메서드를 정의했다. 파이썬에서는 이를 생성자라고 한다.
생성자는 사용자가 호출하지 않아도 객체를 생성할 때 자동으로 호출되는 메서드이다.
클래스를 만들 때 __init__이라는 이름으로 메서드를 정의하면 자동으로 생성자가 된다.
객체를 생성할 때 생성자가 자동으로 호출되므로 self를 제외하고 __init__() 생성자에 정의한 개수만큼 전달값을 넘겨줘야 한다. 전달값이 3개인데 1개 또는 2개만 넘기게 되면 오류가 발생한다.
만약 클래스에 따로 생성자를 정의하지 않았다면, 전달값 없이 클래스명만으로 객체를 생성하면 된다.
형식 : 변수명 = 클래스명()
9.2.2 인스턴스 변수
메서드에 정의한 변수를 인스턴스 변수라고 한다.
Unit 클래스에서는 name, hp, damage가 인스턴스 변수이고, self.name과 같은 형식으로 전달값을 받아 정의한다.
이번에는 클래스 밖에서 인스턴스 변수 정보를 출력해보자. 클래스 안에서는 self.으로 인스턴스 변수에 접근할 수 있었는데, 클래스 밖에서는 객체로 접근한다. 객체명 뒤에 점.을 찍고 인스턴스 변수명을 적으면 된다.
stealth1 = Unit("전투기", 80, 5)
print("유닛 이름 : {0}".format(stealth1.name, stealth1.damage))
'''
전투기유닛을 생성했습니다.
체력 80, 공격력 5
유닛 이름 : 전투기
'''
업그레이드한 전투기만을 위한 특별한 인스턴스 변수를 하나 정의하자. 이름은 cloaking이라고 하고, True일 때는 은폐 상태, False일 때는 일반 상태이다. cloaking 변수를 만들고 True라고 값을 설정해 은폐 상태로 변경하자. if문의 실행결과를 보니 은폐가 제대로 설정됐음을 알 수 있다.
stealth2 = Unit("업그레이드한 전투기", 80, 5)
stealth2.cloaking = True
if stealth2.cloaking == True:
print("{0}는 현재 은폐 상태입니다.".format(stealth2.name))
# 업그레이드한 전투기는 현재 은폐 상태입니다.
이와 같이 클래스로부터 객체를 만든 다음, 객체만을 위한 인스턴스 변수가 필요한 경우에는 클래스 외부에서 별도로 정의할 수 있다. 이때 해당 객체를 제외한 다른 객체들은 새로 정의한 인스턴스 변수를 알지 못하며 사용할 수도 없다. 오직 한 객체만을 위한 인스턴스 변수가 된다.
즉, 인스턴스 변수는 클래스의 메서드에서 정의하거나, 객체를 통해 직접 정의할 수 있다.
9.2.3 메서드
메서드는 클래스 내부에 정의한 함수로, 클래스 안에 여러 개를 만들 수 있다.
메서드가 일반 함수와 다른 점은 전달값 부분에 첫 번째로 self를 넣는다는 점, 메서드 안에서 self.로 인스턴스 변수에 접근할 수 있다는 점이다.
게임에서 공격할 수 있는 유닛만을 위한 새로운 클래스를 정의해보자.
class AttackUnit:
def __init__(self, name, hp, damage):
self.name = name
self.hp = hp #체력
self.damage = damage #공격력
def attack(self, location): #전달받은 방향으로 공격
print("{0} : {1}방향 적군을 공격합니다. [공격력 {2}]".format(self.name, location, self.damage))
def damaged(self, damage) :
print("{0} : {1}만큼 피해를 입었습니다.".format(self.name, damage))
self.hp -= damage #유닛의 체력에서 전달받은 damage만큼 감소
print("{0} : 현재 체력은 {1}입니다.".format(self.name, self.hp)) #남은 체력 출력
if self.hp <= 0: #남은 체력이 0이하면
print("{0} : 파괴됐습니다.".format(self.name)) #유닛 파괴 처리
flamethrower1 = AttackUnit("화염방사병", 50, 16)
flamethrower1.attack("5시") #5시 방향으로 공격 명령
flamethrower1.damaged(25) #25만큼의 공격 2번 받음
flamethrower1.damaged(25) #남은 체력은 0
9.3 클래스 상속하기
파이썬에서의 상속은 한 클래스의 내용을 다른 클래스가 물려받아 사용하는 것을 뜻한다.
9.3.1 상속이란
처음에 만든 Unit 클래스를 수정하자. 모든 유닛은 이름과 체력 정보가 있어야 하므로 name, hp 인스턴스 변수는 남겨두고 공격적인 damage 인스턴스 변수는 없애자. 코드를 간결하게 하기 위해 print()문도 삭제하자.
class Unit:
def __init__(self, name, hp):
self.name = name
self.hp = hp
AttackUnit 클래스는 Unit 클래스의 name, hp 인스턴스 변수를 포함하면서 추가로 damage 인스턴스 변수를 정의하고 있으므로 Unit 클래스로부터 상속받으면 Unit 클래스의 두 가지 인스턴스 변수를 정의하지 않아도 그대로 사용할 수 있다. 이것이 파이썬에서 상속의 의미이다. AttackUnit 클래스의 __init__() 생성자에서 name, hp 인스턴스 변수를 정의하는 부분은 다음과 같이 부모 클래스인 Unit 클래스의 __init__() 생성자를 호출하는 방식으로 코드를 간소화할 수 있다. 이때 self도 같이 넘겨주어야 한다.
다른 클래스로부터 상속을 받을 때는 클래스명 뒤에 소괄호를 적고 상속받을 클래스명을 명시하면 된다. 이때 두 클래스는 자식 클래스와 부모 클래스라고 표현한다.
형식 : class 자식 클래스(부모 클래스):
class AttackUnit(Unit): #Unit클래스 상속
def __init__(self, name, hp, damage):
Unit.__init__(self, name, hp) #부모 클래스의 생성자 호출
self.damage = damage
AttackUnit 클래스는 Unit 클래스의 모든 인스턴스 변수와 메서드를 그대로 사용할 수 있다. 또한 AttackUnit클래스만을 위한 인스턴스 변수와 메서드를 추가할 수도 있다.
상속을 적용한 두 클래스의 전체 코드는 다음과 같다.
class Unit:
def __init__(self, name, hp):
self.name = name
self.hp = hp
class AttackUnit(Unit): #Unit클래스 상속
def __init__(self, name, hp, damage):
Unit.__init__(self, name, hp) #부모 클래스의 생성자 호출
self.damage = damage
def attack(self, location): #전달받은 방향으로 공격
print("{0} : {1} 방향 적군을 공격합니다.[공격력 {2}]".format(self.name, location, self.damage ))
def damaged(self, damage): #damage만큼 유닛 피해
print("{0} : {1}만큼 피해를 입었습니다." .format(self.name, damage))
self.hp -= damage #유닛의 체력에서 전달받은 damage만큼 감소
print("{0} : 현재 체력은 {1}입니다." .format(self.name, self.hp))
if self.hp <= 0: #남은 체력이 0 이하라면
print("{0} : 파괴됐습니다." .format(self.name)) #유닛 파괴 처리
공격 명령 코드를 실행했을 때 어떤 결과가 나오는지 확인해보자.
flamethrower1 = AttackUnit("화염방사병", 50, 16) #이름, hp=50, 데미지=16
flamethrower1.attack("5시") #화염방사병 : 5시 방향 적군을 공격합니다. [공격력 16]
flamethrower1.damaged(25) #화염방사병 : 25만큼 피해를 입었습니다.
#화염방사병 : 현재 체력은 25입니다.
flamethrower1.damaged(25) #화염방사병: 25만큼 피해를 입었습니다.
#화염방사병 : 현재 체력은 0입니다.
#화염방사병 : 파괴됐습니다.
9.3.2 다중 상속
비행 기능을 정의하는 클래스명을 Flyable이라고 하고 __init__()생성자에는 비행할 때 속도를 의미하는 flying_speed를 인스턴스 변수로 넣자. 비행 동작은 fly()메서드로 정의하자. Flyable 클래스는 비행 기능만 제공하므로 어떤 유닛인지에 대한 정보는 포함하지 않겠다. 그 대신 fly()메서드를 호출할 때 유닛의 이름과 비행 방향 정보를 전달 받자.
class Flyable:
def __init__(self, flying_speed): #비행 속도
self.flying_speed = flying_speed
def fly(self, name, location): #유닛 이름, 비행 방향
print("{0} : {1} 방향으로 날아갑니다. [속도 {2}]".format(self.name, location, self.flying_speed))
전투기는 비행하며 공격할 수 있는 공중+공격 유닛이다. 지금까지 만든 클래스 중에서 공격 유닛인 AttackUnit 클래스와 공중 유닛인 Flyable 클래스를 조합하면 만들 수 있다. 이름은 FlyableAttackUnit이라고 하고, AttackUnit클래스와 Flyable클래스를 함께 상속받는다. 이렇게 2개 이상의 클래스를 상속받는 것을 다중상속이라고 하며 다음과 같이 쉼표로 구분해 부모 클래스명을 나열하면 된다.
형식 : class 자식클래스(부모클래스1, 부모클래스2, ...):
그리고 __init__() 생성자 안에서 상속받은 클래스들의 __init__() 생성자를 각각 호출하면 된다.
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name, hp, damage, flying_speed):
AttackUnit.__init__(self, name, hp, damage)
Flyable.__init__(self, flying_speed)
FlyableAttackUnit 클래스로 새로운 객체를 만들고 이름은 interceptor로 하자. 생성자에는 유닛이름, 체력, 공격력, 비행속도 정보를 전달한다. 그런 다음 Flyable클래스에 정의한 fly()메서드를 호출하는데, 이때 이동할 유닛이름과 방향 정보를 전달값으로 넘긴다.
#interceptor라는 새로운 객체 만들기. 이름, hp=200, 데미지=6, 비행속도=5
interceptor = FlyableAttackUnit("요격기",200, 6, 5)
interceptor.fly(interceptor.name, "3시") #요격기 : 3시 방향으로 날아갑니다. [속도 5]
9.3.3 메서드 오버라이딩
게임에서 유닛들은 유닛마다 이동 속도가 다르다. 이번에는 지상 유닛의 이동 속도를 의미하는 speed 인스턴스 변수를 Unit클래스에 추가해보겠다. 그리고 이동 동작을 나타내는 move() 메서드를 정의하고 공중 유닛과 구분하는 문구를 추가해서 어떤 유닛이 몇 시 방향으로 이동하는지를 출력해보겠다.
class Unit:
def __init__(self, name, hp, speed): #speed 추가
self.name = name
self.hp = hp
self.speed = speed #지상 이동 속도
def move(self, location): #이동 동작 정의
print("[지상 유닛 이동]")
print("{0} : {1} 방향으로 이동합니다. [속도 {2}]".format(self.name, location, self.speed))
Unit 클래스를 변경하고 나면 Unit 클래스를 상속받는 자식 클래스에 영향을 끼치게 된다. speed 인스턴스 변수를 새로 추가했으니 __init__()생성자를 사용하는 부분은 변경해줘야 한다.
class AttackUnit(Unit): #Unit클래스 상속
def __init__(self, name, hp, speed, damage): #speed 추가
Unit.__init__(self, name, hp, speed) #speed 추가
self.damage = damage
AttackUnit 클래스가 변경되었으니 AttackUnit 클래스를 상속받는 FlyableAttackUnit 클래스도 수정해야 한다. 공중 공격 유닛은 비행 속도인 flying_speed가 이미 정의되어 있고, 지상에서는 이동하지 못하므로 지상 이동속도를 0으로만 설정한다.
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name, hp, damage, flying_speed):
AttackUnit.__init__(self, name, hp, damage) #지상 이동속도 speed=0
Flyable.__init__(self, flying_speed)
잘 반영됐는지 확인하기 위해 지상 이동속도를 포함한 새로운 공격 유닛을 만들어보자. 이번에 만들 유닛은 지상 유닛 중에서 가장 속도가 빠른 호버바이크이다. AttackUnit 클래스로 호버 바이크 객체를 생성하는데, 전달값으로 지상 이동속도 10을 포함해 체력 80, 공격력 20을 넣자.
#호버바이크 객체 생성 hp(체력)=80, 지상이동속도=10, 데미지(공격력)=20
hoverbike = AttackUnit("호버 바이크", 80, 10, 20)
FlyableAttackUnit 클래스도 수정했으니 공중공격유닛도 만들어보자. 이번에는 유닛 중 가장 강력한 함선으로 압도적인 화력을 자랑하는 우주 순양함을 만들어보자. 우주 순양함은 크기도 엄청나고 체력과 공격력도 굉장히 높다. 체력은 500, 공격력은 25, 비행속도는 3으로 하겠다. 덩치가 워낙 커서 속도는 다소 느리다.
#우주순양함 객체 생성. 공중공격유닛이며 체력과 공격력이 좋음
#체력=500, 비행속도=3, 데미지(공격력)=25
spacecruiser = FlyableAttackUnit("우주 순양함", 500, 3, 25)
새로 만든 두 유닛을 함께 이동시켜 보겠다. 호버바이크는 지상 유닛이므로 move() 메서드로 이동하고, 우주 순양함은 공중 유닛이므로 fly()메서드로 이동하면 된다.
hoverbike.move("11시")
spacecruiser.fly(spacecruiser.name, "9시")
코드를 작성하고 실행해보면 각 유닛의 이동 방향과 속도 정보가 모두 잘 표시된다.
특히 지상 유닛은 이동한다고 표현하고, 공중 유닛은 날아간다고 표현하는 것을 볼 수 있다.
그런데 말입니다~! 아래와 같이 실행결과가 나오면 공중 공격 유닛인 우주 순양함이 [지상 유닛 이동]에 포함되어보인다. 또한, 많은 지상 유닛과 공중 유닛을 이동할 때마다 지상 유닛인지 공중 유닛인지 구분해 move()와 fly()메서드를 적용하는 것은 너무 번거롭다. fly()메서드를 사용할 때 유닛의 이름 정보까지 전달해야하는 불편함도 있다.
이런 경우에 메서드 오버라이딩(method overriding)을 사용할 수 있다!!!!
메서드 오버라이딩은 상속 관계일 때 자식클래스에서 부모클래스에 정의한 메서드를 그대로 사용하지 않고 같은 이름으로 메서드를 새롭게 정의해 사용하는 방법이다.
여기서는 Unit클래스에 정의한 move()메서드를 FlyableAttackUnit 클래스에서 오버라이딩 해보겠다.
메서드 오버라이딩을 할 때는 부모 클래스에 정의한 메서드를 그대로 자식 클래스에서 동일한 이름과 전달값으로 정의하고, 메서드 동작만 원하는 대로 변경하면 된다.
공중 공격 유닛의 이동이므로 메서드 동작에 안내 문구를 추가한다. 그리고 공중으로 날아다니므로 또다른 부모 클래스인 Flyable 클래스에 정의한 fly()메서드를 호출하면 된다. 이때 유닛 이름과 이동할 위치 정보를 함께 전달한다.
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name, hp, damage, flying_speed):
AttackUnit.__init__(self, name, hp, 0, damage) #지상 이동속도 speed=0
Flyable.__init__(self, flying_speed)
#이 부분을 새로 추가한 거임
def move(self, location): #Unit 클래스의 move() 메서드를 오버라이딩
print("[공중 유닛 이동]")
self.fly(self.name, location)
메서드를 새로 정의했으니 제대로 적용되는지 다시 한 번 유닛들을 이동시켜 보자.
이번에는 지상과 공중 구분 없이 모두 move()로만 이동하겠다. fly()메서드를 사용할 때와 달리 유닛 이름까지 전달해야 하는 번거로움을 줄일 수 있다.
hoverbike.move("11시")
spacecruiser.move("9시") #오버라이딩한 move()메서드 호출
# = spacecruiser.fly(spacecruiser.name, "9시")
실행해보니 지상 유닛인 호버 바이크는 이동한다고 표현하고, 공중 유닛인 우주 순양함은 날아간다고 표현하는 걸 보니 두 유닛 모두 올바르게 출력된다.
다음 그림을 보면 상속 관계는 변함이 없지만 Unit 클래스에 move()메서드를 정의함으로써 Unit 클래스로 생성한 객체들은 모두 move()메서드를 사용해 지상에서 이동할 수 있다.
하지만 공중 공격 유닛인 FlyableAttackUnit 클래스로 생성한 객체들은 지상 이동이 아닌 공중에서 비행한다. 따라서 Flyable클래스의 fly()메서드를 사용해야 한다. 그런데 유닛이 많아지면 개별적으로 관리하기가 어려우므로 move()메서드를 오버라이딩 해서 재정의한 메서드에서 fly()를 호출하도록 바꿨다.
이렇게 하면 같은 move() 메서드를 호출하더라도 AttackUnit 클래스로 만들어진 유닛은 부모 클래스인 Unit 클래스의 move() 메서드를, FlyableAttackUnit 클래스로 만들어진 유닛은 오버라이딩한 FlyableAttackUnit 클래스의 move() 메서드를 호출하게 된다.
지금까지 작성한 전체 코드!
class Unit:
def __init__(self, name, hp, speed): #speed 추가
self.name = name
self.hp = hp
self.speed = speed #지상 이동 속도
def move(self, location): #이동 동작 정의
print("[지상 유닛 이동]")
print("{0} : {1} 방향으로 이동합니다. [속도 {2}]".format(self.name, location, self.speed))
class AttackUnit(Unit): #Unit클래스 상속
def __init__(self, name, hp, speed, damage):
Unit.__init__(self, name, hp, speed) #부모 클래스의 생성자 호출
self.damage = damage
def attack(self, location): #전달받은 방향으로 공격
print("{0} : {1} 방향 적군을 공격합니다.[공격력 {2}]".format(self.name, location, self.damage ))
def damaged(self, damage): #damage만큼 유닛 피해
print("{0} : {1}만큼 피해를 입었습니다." .format(self.name, damage))
self.hp -= damage #유닛의 체력에서 전달받은 damage만큼 감소
print("{0} : 현재 체력은 {1}입니다." .format(self.name, self.hp))
if self.hp <= 0: #남은 체력이 0 이하라면
print("{0} : 파괴됐습니다." .format(self.name)) #유닛 파괴 처리
class Flyable:
def __init__(self, flying_speed): #비행 속도
self.flying_speed = flying_speed
def fly(self, name, location): #유닛 이름, 비행 방향
print("{0} : {1} 방향으로 날아갑니다. [속도 {2}]".format(self.name, location, self.flying_speed))
class FlyableAttackUnit(AttackUnit, Flyable):
def __init__(self, name, hp, damage, flying_speed):
AttackUnit.__init__(self, name, hp, 0, damage) #지상 이동속도 speed=0
Flyable.__init__(self, flying_speed)
#이 부분을 새로 추가한 거임
def move(self, location): #Unit 클래스의 move() 메서드를 오버라이딩
print("[공중 유닛 이동]")
self.fly(self.name, location)
#interceptor라는 새로운 객체 만들기. 이름, hp=200, 데미지=6, 비행속도=5
interceptor = FlyableAttackUnit("요격기",200, 6, 5)
interceptor.fly(interceptor.name, "3시") #요격기 : 3시 방향으로 날아갑니다. [속도 5]
#호버바이크 객체 생성. 지상유닛이며 기동성이 좋음
#hp(체력)=80, 지상이동속도=10, 데미지(공격력)=20
hoverbike = AttackUnit("호버 바이크", 80, 10, 20)
#우주순양함 객체 생성. 공중유닛이며 체력과 공격력이 좋음
#체력=500, 비행속도=3, 데미지(공격력)=25
spacecruiser = FlyableAttackUnit("우주 순양함", 500, 3, 25)
hoverbike.move("11시")
spacecruiser.move("9시") #오버라이딩한 move()메서드 호출
# = spacecruiser.fly(spacecruiser.name, "9시")
9.4 동작 없이 일단 넘어가기 : pass
이번에는 건물 유닛을 짓기 위한 클래스를 만들어보자. 건물 유닛도 일반 유닛처럼 이름과 체력이 있어서 적군으로부터 공격받아 체력이 0이 되면 파괴된다. Unit 클래스에 공통 속성이 있으니 다른 유닛과 마찬가지로 이를 상속받는다. 건물 유닛을 지을 때는 어느 위치에 지을지를 플레이어가 정하는데, 이를 location이라고 하자.
건물 유닛을 클래스로 정의할 때 __init__() 생성자의 세부 내용은 일단 내버려두겠다. (다른 작업 먼저 하고 나중에 코드를 작성하려고!) 이럴 때 파이썬에서는 pass를 사용한다. pass는 아무것도 하지 않고 일단 그냥 넘어간다는 의미로 사용한다. 6.2.3 흐름 제어하기에서 배운 continue와 break의 역할을 떠올려 보면 된다.
#건물 유닛
class BuildingUnit(Unit):
def __init__(self, name, hp, location):
pass
#보급고 : 건물유닛, 1개 건물 유닛 = 8유닛
supply_depot = BuildingUnit("보급고", 500, "7시") #체력 500, 생성 위치 7시
pass는 다른 곳에서도 사용할 수 있다. 앞의 코드에 이어서 함수 2개를 만들어 보겠다. 게임 시작을 알리는 game_start()함수와 게임 종료를 알리는 game_over()함수이다. game_start()에서는 실행할 문장 부분에 print()문을 안내 문구를 출력하지만, game_over()에서는 pass만 작성한다. 함수 뿐만 아니라 if문, for문, while문 등에서도 pass로 당장은 세부 동작을 정의하지 않은 채로 뒀다가 나중에 다시 코드를 완성할 수 있다.
def game_start():
print("[알림] 새로운 게임을 시작합니다.")
def game_over():
pass
game_start() #[알림] 새로운 게임을 시작합니다.
game_over()
9.5 부모 클래스 호출하기 : super()
건물 유닛 클래스를 만들 때 pass로만 남겨둔 __init__() 생성자 코드를 완성해보겠다. Unit 클래스를 상속받으므로 Unit 클래스의 __init__() 생성자를 활용하면 된다. 건물은 이동할 수 없으므로 speed 정보는 0으로 하고 다음 줄에서 location 인스턴스 변수를 정의한다.
#건물 유닛
class BuildingUnit(Unit):
def __init__(self, name, hp, location):
Unit.__init__(self, name, hp, 0) #수정한 부분
self.location = location #수정한 부분
클래스에서도 이름을 직접 적지 않고도 부모 클래스에 접근하는 방법이 있다. 바로 super() 이다.
super()는 상속하는 부모 클래스의 메서드를 사용할 때 필요하다.
단, super()를 사용하는 문장에서는 self를 함께 사용하지 않으니 이 점을 주의해야 한다.
class BuildingUnit(Unit):
def __init__(self, name, hp, location):
#Unit.__init__(self, name, hp, 0) 이를 super로 고칠 수 있음
super().__init__(name, hp, 0) #부모 클래스 접근. self 없이 사용
self.location = location
부모 클래스를 2개 이상 상속하는 다중 상속의 경우에는 어떨까?
일반 유닛인 Unit클래스와 비행기능인 Flyable클래스를 정의한다. 이 둘을 부모 클래스로 하는 공중 유닛인 FlyableUnit을 정의하고 생성자에서 super()로 부모클래스의 생성자를 호출하게 한다. 그리고 공중유닛의 객체인 troopship을 생성하고 코드를 실행해보자.
class Unit:
def __init__(self):
print("Unit 생성자")
class Flyable:
def __init__(self):
print("Flyable 생성자")
class FlyableUnit(Unit, Flyable):
def __init__(self):
super().__init__()
#수송선
troopship = FlyableUnit()
실행결과는 다음과 같다. 부모클래스는 분명 Unit과 Flyable인데, super()생성자를 호출했을 때 Unit 클래스의 생성자가 호출된다.
부모클래스의 상속 순서를 Flyable, Unit으로 바꿔서 다시 실행해보자.
class FlyableUnit(Flyable,Unit): #상속순서 변경
이번에는 Flyable클래스의 생성자가 호출되는 것을 볼 수 있다.
즉, 다중상속을 받은 클래스에서 super()로 부모클래스에 접근할 때는 순서 상 가장 먼저 상속받은 클래스에 접근하게 된다. 그러므로 다중상속을 할 때 모든 부모클래스의 생성자를 호출하려면 super()를 사용하지 않고 다음과 같이 각 부모클래스의 이름을 명시해서 접근해야 한다.
class Unit:
def __init__(self):
print("Unit 생성자")
class Flyable:
def __init__(self):
print("Flyable 생성자")
class FlyableUnit(Unit, Flyable):
def __init__(self):
#super().__init__() 다중상속일 때는 super()를 사용하지 않는다.
Unit.__init__(self) #Unit클래스 생성자 호출
Flyable.__init__(self) #Flyable클래스 생성자 호출
#수송선
troopship = FlyableUnit()
실행해보면 각 부모클래스의 이름을 명시해서 접근하니 부모클래스의 생성자를 모두 호출하는 것을 볼 수 있다.
9.6 게임 완성
클래스 변수는 클래스명 바로 밑에 정의하고 클래스로부터 만들어진 모든 객체에 값이 일괄 적용된다.
인스턴스 변수는 클래스의 메서드에 정의하거나 객체를 통해 직접 정의하며, 객체마다 서로 다른 값을 가질 수 있다.
class Tank(ATtackUnit):
siege_developed = False #시지모드 개발여부, 클래스변수로 정의
def __init__(self):
AttackUnit.__init__(self,"탱크",150,35,1)
self.siege_mode = False #시지모드 해제상태, 인스턴스변수로 정의
9.7 게임 최종 리뷰
게임 프로젝트에 사용한 모든 클래스의 상속 관계를 정리하면 다음과 같다.
#게임 시작
def game_start():
print("[알림] 새로운 게임을 시작합니다.")
#게임 종료
def game_over():
print("Player : Good Game")
print("[Player]님이 게임에서 퇴장했습니다.")
#실제 게임 진행
game_start()
#보병 3기 생성
so1 = Soldier()
so2 = Soldier()
so3 = Soldier()
#탱크 2기 생성
ta1 = Tank()
ta2 = Tank()
#전투기 1기 생성
st1 = Stealth()
#유닛 일괄 관리(생성된 모든 유닛 추가)
attack_units = []
attack_units.append(so1)
attack_units.append(so2)
attack_units.append(so3)
attack_units.append(ta1)
attack_units.append(ta2)
attack_units.append(st1)
#전군 이동
for unit in attack_units:
unit.move("1시")
#탱크 시지 모드 개발
Tank.siege_developed = True
print("[알림] 탱크의 시지 모드 개발이 완료되었습니다.")
#공격 모드 준비(보병 : 강화제, 탱크 : 시지모드, 전투기 : 은폐모드)
for unit in attack_units:
if isinstance(unit, Soldier): #Soldier클래스의 인스턴스라면 강화제
unit.booster()
elif isinstance(unit, Tank): #Tank클래스의 인스턴스이면 시지모드
unit.set_siege_mode()
elif isinstance(unit, Stealth): #Stealth클래스의 인스턴스이면 은페모드
unit.cloaking()
#전군 공격
for unit in attack_units:
unit.attck("1시")
#전군 피해
for unit in attack_units:
unit.damaged(randint(5,20)) #피해는 무작위로 받음(5~20)
#게임 종료
game_over()
9.8 실습 문제 : 부동산 프로그램 만들
class House:
#매물 초기화 : 위치, 건물 종류, 매물 종류, 가격, 준공연도
def __init__(self, location, house_type, deal_type, price, completion_year):
self.location = location
self.house_type = house_type
self.deal_type = deal_type
self.price = price
self.completion_year = completion_year
#매물 정보 표시
def show_detail(self):
print(self.location, self.house_type, self.deal_type, self.price, self.completion_year)
houses = []
house1 = House("강남", "아파트", "매매", "10억원", "2010년")
house2 = House("마포", "오피스텔", "전세", "5억원", "2007년")
house3 = House("송파", "빌라", "월세", "500/50만원", "2000년")
houses.append(house1)
houses.append(house2)
houses.append(house3)
print("총 {0}가지 매물이 있습니다.".format(len(houses)))
for house in houses:
house.show_detail()