Programming Language/Python

[Python] 기본 문법(12): 클래스

lxvxxu 2024. 7. 20. 22:55

 

클래스(class)

서로 관련 있는 (1) 변수(인스턴스 변수)(2) 함수(메서드)들의 집합

 

비유
클래스는 붕어빵 틀과 유사하다고 볼 수 있다.
붕어빵을 만들 때 틀에 반죽과 속재료를 넣고 불에 구우면 똑같은 모양의 붕어빵을 여러 개 만들 수 있다.
그리고 반죽과 속재료를 바꿔도 항상 같은 모양의 붕어빵이 만들어진다.

클래스명
일반적으로 하나 or 여러 단어의 조합으로 만드는데, 각 단어의 첫 글자는 대문자로 작성한다.

 

(1) Object (객체) = Class의 Instance (변수)

위 코드에서 만들어진 유닛들 soldier1, soldier2, tank를 객체라고 하고

이렇게 만들어진 객체를 클래스의 인스턴스라고 한다.

 

보통 객체를 단독으로 부를 때는 '객체'로, Class와 연결지어 부를 때는 Instance로 표현한다.

 

(2) method (메서드)

클래스 안에는 필요한 함수를 정의하는데, 클래스 안에 정의하는 함수를 특별히 method라고 한다.

 

 

※ self

    일반 함수와 다르게 첫 번째 전달값 위치에는 self라고 넣는다.

형식
class 클래스명:
    def 메서드명1(self, 전달값1, 전달값2, . . .):
        실행할 명령1
        실행할 명령2
        . . .


    def 메서드명2(self, 전달값1, 전달값2, . . .):
        실행할 명령1
        실행할 명령2
        . . .

 

※ 전달값 받는 변수 정의

self.변수명 = 값

※ self
self는 객체인 자기 자신을 의미
self 를 전달값에 넣는다 = 객체를 받는다

메서드 안에서 self.를 사용한다 = 객체의 인스턴스 변수 or method에 접근하겠다.
 

flamethrower1 =  AttackUnit 클래스의 인스턴스
flamethrower1 객체를 생성할 때는 name, hp, damage 정보만 전달하지만,
자동으로 호출되는 __init__() 생성자의 첫 번째 전달값에 있는 self flamethrower1 객체도 전달한다.
그래서 생성자 안에 작성한 self.name = name flamethrower1.name = name과 같은 의미

 
복잡하고 이해하기 어렵다면 2가지만 기억하세요.
첫째, 클래스의 메서드에는 첫 번째 전달값으로 self를 적어야 합니다. (객체를 받기 위해)
둘째, 클래스 안에서는 변수 또는 메서드에 접근하려면 self.name 또는 self.attack(...)처럼
        인스턴스 변수 또는 메서드명 앞에 self.을 함께 적어야 합니다.

 

 

 

생성자(constructor): __init__() 메서드

객체를 생성할 때는 self를 제외하고 __init__() 생성자에 정의한 개수만큼 전달값을 넘겨 줘야 한다.

만약 클래스에 따로 생성자를 정의하지 않았다면 전달값 없이 클래스명만으로 객체를 생성하면 된다.

 

  코드를 작성할 때 문장이 너무 길어서 한 줄로 표현하기 어렵거나 보기 좋게 두 줄 이상으로 나눠 적으려면 나누려는 부분에 역슬래시(\)를 넣고 줄 바꿈을 하면 된다. 그러면 실행했을 때 한 문장으로 인식함

상속(inheritance)

한 클래스의 내용을 다른 클래스가 물려받아 사용하는 것

인스턴스 변수를 정의하지 않아도 자식 클래스는 부모 클래스의 변수를 그대로 사용할 수 있다.

 

장점

서로 관련 있는 클래스들에서 공통 부분을 모아 부모 클래스로 정의하고 자식 클래스에서는 필요한 부분을 확장해 사용하면 불필요한 코드의 중복 작성을 방지할 수 있고 수정이나 추가 사항이 생길 때 작업 범위를 최소화할 수 있다.

 

형식
class 자식 클래스(부모 클래스):

 

 

다중 상속 (multiple inheritance)

 

형식

class 자식 클래스(부모 클래스1, 부모 클래스2, . . .):

https://thebook.io/080357/0347/

 

# class 학습
# class : 서로 관련 있는 (인스턴스) 변수와 메서드들의 집합


# 일반 유닛
class Unit:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp


# 공격 유닛 
class AttackUnit(Unit):
    def __init__(self, name, hp, damage):   # 함수
        Unit.__init__(self, name, hp)   # 부모 클래스의 생성자 호출
        self.damage = damage    # self.damage = damage는 flamethrower1 = 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
        
        # 남은 체력 출력
        print("{0} : 현재 체력은 {1}입니다.".format(self.name, self.hp))
        if self.hp <= 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(name, location, self.flying_speed))


# 공중 공격 기능 | AttackUnit 클래스와 Flyable 클래스를 함께 상속
# multiple inheritance

class FlyableAttackUnit(AttackUnit, Flyable):
    def __init__(self, name, hp, damage, flying_speed):
        AttackUnit.__init__(self, name, hp, damage)
        Flyable.__init__(self, flying_speed)



# 화염방사병 : 공격 유닛, 화염방사기를 사용함
flamethrower1 = AttackUnit("화염방사병", 50, 16)
# 객체 생성, 체력 50, 공격력 16
# flamethrower1은 객체이자 클래스의 Instance이다.

# 객체 생성 시에는 name, hp, damage 정보만 전달하지만
# 자동으로 호출되는 __init__() 생성자의 첫 번째 전달값에 있는 self에
# flamethrower1 객체도 전달하고 있다.
flamethrower1.attack("5시")

flamethrower1.damaged(25)
flamethrower1.damaged(25)


# 요격기: 공중 공격 유닛, 미사일 여러 발을 한 번에 발사
# 유닛 이름, 체력, 공격력, 비행 속도
interceptor = FlyableAttackUnit("요격기", 200, 6, 5)
interceptor.fly(interceptor.name, "3시")

 


메서드 오버라이딩 (메서드 재정의, method overriding)

상속 관계일 때 자식 클래스에서 부모 클래스에 정의한 메서드를 그대로 사용하지 않고

같은 이름으로 메서드를 새롭게 정의해 기존 동작을 개선하거나 새로운 동작을 수행하도록 하는 방법.

 

메서드 오버라이딩 할 때에는 부모 클래스에 정의한 메서드를 그대로 자식 클래스에서 동일한 이름과 전달값으로 정의하고, 메서드 동작만 원하는 대로 변경하면 된다.

https://thebook.io/080357/0354/

 

 

(FlyableAttackUnit Class에 move() 함수를 추가한) 코드

move()함수를 이용해야만 fly() 함수 사용 시 유닛 이름을 전달하지 않아도 됨.

# class 학습
# class : 서로 관련 있는 (인스턴스) 변수와 메서드들의 집합


# 일반 유닛
class Unit:
    def __init__(self, name, hp, speed):
        self.name = name
        self.hp = hp
        self.speed = speed

    def move(self, location):   # location은 객체에 저장할 데이터가 아니라서 따로 지정(이후 소멸)
        print("[지상 유닛 이동]")
        print("{0} : {1} 방향으로 이동합니다. [속도 {2}]"\
              .format(self.name, location, self.speed))


# 공격 유닛 
class AttackUnit(Unit):
    def __init__(self, name, hp, damage, speed):   # 함수
        Unit.__init__(self, name, hp, speed)   # 부모 클래스의 생성자 호출
        self.damage = damage    # self.damage = damage는 flamethrower1 = 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
        
        # 남은 체력 출력
        print("{0} : 현재 체력은 {1}입니다.".format(self.name, self.hp))
        if self.hp <= 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(name, location, self.flying_speed))


# 공중 공격 기능 | AttackUnit 클래스와 Flyable 클래스를 함께 상속
# multiple inheritance

class FlyableAttackUnit(AttackUnit, Flyable):
    def __init__(self, name, hp, damage, flying_speed):
        AttackUnit.__init__(self, name, hp, damage, 0)
        Flyable.__init__(self, flying_speed)

    def move(self, location):
        print("[공중 유닛 이동]")
        self.fly(self.name, location)



# 화염방사병 : 공격 유닛, 화염방사기를 사용함
flamethrower1 = AttackUnit("화염방사병", 50, 16, 2)
# 객체 생성, 체력 50, 공격력 16
# flamethrower1은 객체이자 클래스의 Instance이다.

# 객체 생성 시에는 name, hp, damage 정보만 전달하지만
# 자동으로 호출되는 __init__() 생성자의 첫 번째 전달값에 있는 self에
# flamethrower1 객체도 전달하고 있다.
flamethrower1.attack("5시")

flamethrower1.damaged(25)
flamethrower1.damaged(25)


# 요격기: 공중 공격 유닛, 미사일 여러 발을 한 번에 발사
# 유닛 이름, 체력, 공격력, 비행 속도
interceptor = FlyableAttackUnit("요격기", 200, 6, 5)
interceptor.fly(interceptor.name, "3시")


# 호버 바이크: 지상 유닛, 가독성 좋음
hoverbike = AttackUnit("호버 바이크", 80, 20, 10)


# 우주 순양함: 공중 유닛, 체력도 굉장히 좋음, 공격력도 좋음
spacecruiser = FlyableAttackUnit("우주 순양함", 500, 25, 3)


hoverbike.move("11시")
# fly() 메서드를 사용
# spacecruiser.fly(spacecruiser.name, "9시")

# 오버라이딩한 move() 메서드 호출
spacecruiser.move("9시")

 

 

 

(FlyableAttackUnit Class에 move() 함수를 추가하지 않은) 코드

Flyable() 함수에 Unit Class를 상속시켜 

move() 함수를 사용하지 않고도 fly() 함수를 사용할 때 유닛(객체, 클래스의 인스턴스)의 이름을 전달하지 않아도 된다.

 

# class 학습
# class : 서로 관련 있는 (인스턴스) 변수와 메서드들의 집합


# 일반 유닛
class Unit:
    def __init__(self, name, hp, speed):
        self.name = name
        self.hp = hp
        self.speed = speed

    def move(self, location):   # location은 객체에 저장할 데이터가 아니라서 따로 지정(이후 소멸)
        print("[지상 유닛 이동]")
        print("{0} : {1} 방향으로 이동합니다. [속도 {2}]"\
              .format(self.name, location, self.speed))


# 공격 유닛 
class AttackUnit(Unit):
    def __init__(self, name, hp, damage, speed):   # 함수
        Unit.__init__(self, name, hp, speed)   # 부모 클래스의 생성자 호출
        self.damage = damage    # self.damage = damage는 flamethrower1 = 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
        
        # 남은 체력 출력
        print("{0} : 현재 체력은 {1}입니다.".format(self.name, self.hp))
        if self.hp <= 0:
            print("{0} : 파괴됐습니다.".format(self.name))  # 유닛 파괴 처리


# 비행 기능
class Flyable(Unit):
    def __init__(self, name, hp, speed):
        Unit.__init__(self, name, hp, speed)

    def fly(self, location):
        print("{0} : {1} 방향으로 날아갑니다. [속도 {2}]"\
              .format(self.name, location, self.speed))


# 공중 공격 기능 | AttackUnit 클래스와 Flyable 클래스를 함께 상속
# multiple inheritance

class FlyableAttackUnit(AttackUnit, Flyable):
    def __init__(self, name, hp, damage, speed):
        AttackUnit.__init__(self, name, hp, damage, speed)
        # Flyable.__init__(self, name, speed)

    # def move(self, location):
    #     print("[공중 유닛 이동]")
    #     self.fly(self.name, location)



# 화염방사병 : 공격 유닛, 화염방사기를 사용함
flamethrower1 = AttackUnit("화염방사병", 50, 16, 2)
# 객체 생성, 체력 50, 공격력 16
# flamethrower1은 객체이자 클래스의 Instance이다.

# 객체 생성 시에는 name, hp, damage 정보만 전달하지만
# 자동으로 호출되는 __init__() 생성자의 첫 번째 전달값에 있는 self에
# flamethrower1 객체도 전달하고 있다.
flamethrower1.attack("5시")

flamethrower1.damaged(25)
flamethrower1.damaged(25)


# 요격기: 공중 공격 유닛, 미사일 여러 발을 한 번에 발사
# 유닛 이름, 체력, 공격력, 비행 속도
interceptor = FlyableAttackUnit("요격기", 200, 6, 5)
interceptor.fly("3시")


# 호버 바이크: 지상 유닛, 가독성 좋음
hoverbike = AttackUnit("호버 바이크", 80, 20, 10)


# 우주 순양함: 공중 유닛, 체력도 굉장히 좋음, 공격력도 좋음
spacecruiser = FlyableAttackUnit("우주 순양함", 500, 25, 3)


hoverbike.move("11시")
# fly() 메서드를 사용
# spacecruiser.fly(spacecruiser.name, "9시")
spacecruiser.fly("9시")

# 오버라이딩한 move() 메서드 호출
#spacecruiser.move("9시")

 

 


 

pass (동작 없이 일단 넘어가기)

당장은 세부 동작을 정의하지 않은 채로 뒀다가 나중에 다시 코드를 완성

 

# 건물 유닛
class BuildingUnit(Unit):
	def __init__(self, name, hp, location):
    	pass
        
# 보급고: 건물 유닛, 1개 건물 유닛 = 8유닛
supply_depot = BuildingUnit("보급고", 500, "7시")

def game_start():
	print("[알림] 새로운 게임을 시작합니다.")
    
def game_over():
	pass
    
game_start()
game_over()

 

 

 

super(): 부모 클래스 호출하기

클래스에서 이름을 직접 적지 않고도 부모 클래스에 접근하는 방법이 있다.

상속하는 부모 클래스의 메서드를 사용할 때 super()가 필요하다.

class BuildingUnit(Unit):
    def __init__(self, name, hp, location):
  	  super().__init__(name, hp, 0) # 부모 클래스 접근, self 없이 사용
	    self.location = location

 

 

※ 다중 상속 (multiple inheritance)을 받은 클래스에서 super()로 부모 클래스에 접근할 때는 순서상 가장 먼저 상속받은 클래스에 접근하게 된다. 그러므로 다중 상속을 할 때 모든 부모 클래스의 생성자를 호출하려면 super()를 사용하지 않고, 다음과 같이 각 부모 클래스의 이름을 명시해서 접근해야 한다.

(그렇지 않으면 하나의 클래스만 상속받기 때문이다.)

class Unit:
	def __init__(self):
    	print("Unit 생성자")

clss Flyable:
	def __init__(self):
    	print("Flyable 생성자")

class FlyableUnit(Flyable, Unit):
    def __init__(self):
    # super().__init__()
    Unit.__init__(self) # Unit 클래스 생성자 호출
    Flyable.__init__(self) # Flyable 클래스 생성자 호출
    
troopship = FlyableUnit()

 

 

== 최종 코드 ==

https://thebook.io/080357/0367/

 

코딩 자율학습 나도코딩의 파이썬 입문: 9.6.1 게임 준비하기

더북(TheBook): (주)도서출판 길벗에서 제공하는 IT 도서 열람 서비스입니다.

thebook.io

 

수행할 동작을 순차적으로 정리해 보겠습니다.

• 게임 시작

• 유닛 생성(보병 3기, 탱크 2기, 전투기 1기)

• 전군 1시 방향으로 이동

• 탱크 시지 모드 개발

• 공격 준비(보병 강화제, 탱크 시지 모드, 전투기 은폐 모드)

• 전군 1시 방향 공격

• 전군 피해

• 게임 종료

동작을 하나씩 코드로 작성해 보겠습니다.