본문 바로가기
Python 기초

[Python] 클래스 정리

by 내 코딩 2023. 3. 29.
반응형

클래스는 붕어빵 틀에 비유하곤 한다.

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

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

단, 일반 함수와 다르게 첫 번째 전달값 위치에는 self라고 넣는다는 점을 주의해야한다.

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

메서드 안에는 전달값을 받는 변수를 정의한다.

이 때 변수는 다음과 같은 형식으로 정의한다.

self.변수명 = 값

이렇게 메서드 안에 정의한 변수를 인스턴스 변수라고 한다.

클래스 안에서 사용하는 변수라고 보면 될 것 같다.

class Unit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
        print(f'{self.name} 유닛을 생성했습니다.')
        print(f'체력 {self.hp}, 공격력 {self.damage}')

클래스도 함수와 마찬가지로 정의만 해서는 아무런 동작도 하지 않는다.

소괄호 안에는 클래스의 __init__() 메서드에 정의한 부분 중 self를 제외한 나머지를 전달값에 넣는다.

객체명 = 클래스명(전달값1, 전달값2, ...)  # self를 제외한 나머지 전달값
class Unit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage
        print(f'{self.name} 유닛을 생성했습니다.')
        print(f'체력 {self.hp}, 공격력 {self.damage}')


soldier1 = Unit('보병', 40, 5)
soldier2 = Unit('보병', 40, 5)
tank = Unit('탱크', 150, 35)

클래스 하나로 서로 다른 유닛 3개를 만들었다.

이렇게 만들어진 유닛들을 객체(object)라고 한다. 그리고 이렇게 만들어진 객체를 클래스의 인스턴스(instance)라고 한다.

즉, soldier1, soldier2, tank 객체는 Unit 클래스의 인스턴스이다.

 

생성자: __init__()

Unit 클래스에 __init__() 메서드를 정의했다. 파이썬에서는 이를 생성자(constructor)라고 한다.

생성자는 사용자가 따로 호출하지 않아도 객체를 생성할 때 자동으로 호출되는 메서드다.

 

인스턴스 변수

메서드에서 정의한 변수를 인스턴스 변수라고 하며 self와 함께 사용한다고 했다.

Unit 클래스에서는 name, hp, damage가 인스턴스 변수이고, self.name과 같은 형식으로 전달값을 받아 정의한다.

클래스 밖에서 인스턴스 변수 정보를 출력하기

클래스 안에서는 self.으로 인스턴스 변수에 접근할 수 있었는데, 클래스 밖에서는 객체로 접근한다.

객체로 접근할 때는 객체명 뒤에 점(.)을 찍고 인스턴스 변수명을 적으면 된다.

stealth1 = Unit('전투기', 80, 5)
print(f'유닛 이름: {stealth1.name}, 공격력: {stealth1.damage}')

객체명이 stealth1이므로 이 객체의 인스턴스 변수에는 stealth1.name과 stealth1.damage로 접근해 값을 출력한다.

 

이번에는 은폐 기능이 있는 전투기를 하나 더 만든다.

stealth2 = Unit('전투기', 80, 5)
stealth2.cloaking = True

그런데 Unit 클래스의 인스턴스 변수에는 name, hp, damage만 있어서 은폐 상태를 관리할 수가 없다.

그래서 업그레이드한 전투기만을 위한 특별한 인스턴스 변수를 하나 정의한다.

if stealth2.cloaking == True:
    print(f'{stealth2.name}는 현재 은폐 상태입니다.')

메서드

메서드는 클래스 내부에 정의한 함수로, 클래스 안에 여러 개를 만들 수 있다.

메서드가 일반 함수와 다른 점은 전달값 부분에 첫 번째로 self를 넣는다는 점

메서드 안에서 self.으로 인스턴스 변수에 접근할 수 있다는 점이다.

 

이번에는 공격할 수 있는 유닛만을 위한 새로운 클래스를 정의한다.

공격  명령을 내리면 공격하는 동작, 적군으로부터 공격받으면 피해를 입는 동작 등을 정의한다.

class AttackUnit:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage

    def attack(self, location):
        print(f'{self.name}: {location} 방향 적군을 공격합니다. [공격력 {self.damage}]')

    def damaged(self, damage):
        print(f'{self.name} : {damage}만큼 피해를 입었습니다.')
        self.hp -= damage
        print(f'{self.name} : 현재 체력은 {self.hp}입니다.')
        if self.hp <= 0:
            print(f'{self.name} : 파괴됐습니다.')

화염방사병이라는 유닛을 생성해 공격을 한다.

flamethrower1 = AttackUnit('화염방사병', 50, 16)
flamethrower1.attack('5시')

명령대로 5시 방향을 공격한다.

 

화염방사병이 피해를 입는 상황이다.

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

체력이 0이하가 되면 파괴가 된다.

self
self는 객체인 자기 자신을 의미한다. 생성자 또는 메서드에서 self를 전달값에 넣는다는 것은 결국 객체를 받는다는 뜻이다. 그래서 메서드 안에서 self.을 사용하는 것은 객체의 인스턴스 변수 또는 메서드에 접근하겠다는 의미가 된다.

 

클래스 상속하기

상속(inheritance)은 자식이 부모로부터 재산을 물려받는 것을 의미한다. 파이썬에서 상속은 한 클래스의 내용을 다른 클래스가 물려받아 사용하는 것을 뜻한다.

상속이란?

메서드에서 유닛 중에서 공격할 수 없는 유닛도 있다.

하지만 AttackUnit 클래스로 생성할 수 없다. 그래서 처음에 만든 Unit 클래스를 수정한다.

damage 인스턴스 변수를 포함해 공격과 관련된 내용은 AttackUnit 클래스에 있으므로 Unit 클래스는 일반적인 유닛을 위한 클래스로 수정한다. 모든 유닛은 이름과 체력 정보가 있어야 하므로 name, hp 인스턴스 변수는 남겨두고 공격력인 damage 인스턴스 변수는 없앤다.

 

수정한 Unit 클래스

class Unit:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

AttackUnit 클래스에서 __init__() 생성자 부분만 보면 Unit 클래스와 겹치는 부분이 있다.

Unit 클래스로부터 상속받으면 Unit 클래스의 name, hp 인스턴스 변수를 정의하지 않아도 그대로 사용할 수 있다.

다른 클래스로부터 상속받을 때는 클래스명 뒤에 소괄호를 적고 상속받을 클래스명을 명시하면 된다. 이 때 두 클래스는 실생활에서 자식이 부모로부터 상속받는 관계와 비슷해서 자식 클래스부모 클래스라고 표현한다.

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

수정한 AttackUnit 클래스

class AttackUnit:
    def __init__(self, name, hp, damage):
        Unit.__init__(self, name, hp)
        self.damage = damage

다중 상속

공중 유닛은 날 수 있지만 지상 유닛에는 적용될 수 없으니 비행 가능을 정의하는 클래스를 별로도 만든다.

class Flyable:
    def __init__(self, flying_speed):
        self.flying_speed = flying_speed
        
    def fly(self, name, location):
        print(f'{name} : {location} 방향으로 날아갑니다.')

공중 + 공격 유닛을 합쳐서 공중 공격 유닛을 만들 수 있다.

공중 공격 유닛을 만들기 위해 FlyableAttackUnit 클래스를 만들고 AttackUnit 클래스와 Flyable 클래스를 상속 받는다.

이렇게 2개 이상 상속받는 것을 다중 상속(multiple inheritance)이라고 하고 쉼표로 구분해 부모 클래스명을 나열한다.

class 자식 클래스(부모 클래스1, 부모 클래스2, ...):
class FlyableAttackUnit(AttackUnit, Flyable):
    def __init__(self, name, hp, damage, flying_speed):
        AttackUnit.__init__(self, name, hp, damage)
        Flyable.__init__(self, flying_speed)


interceptor = FlyableAttackUnit('요격시', 200, 6, 5)
interceptor.fly(interceptor.name, '3시')

FlyableAttackUnit 클래스로 새로운 객체를 만들고 이름은 interceptor로 한다.

생성자에는 유닛 이름, 체력, 공격력, 비행 속도 정보를 전달한다.

그런 다음 Flyable 클래스에 정의한 fly() 메서드를 호출하는데, 이 때 이동할 유닛 이름과 방향 정보를 전달값으로 넘긴다.

 

메서드 오버라이딩

이번에는 지상 유닛의 이동 속도를 의미하는 speed 인스턴스 변수를 Unit 클래스에 추가한다.

그리고 이동 동작을 나타내는 move() 메서드를 정의하고 공중 유닛과 구분하는 문구를 추가해서 어떤 유닛이 몇 시 방향으로 이동하는지를 출력한다.

 

class Unit:
    def __init__(self, name, hp, speed):
        self.name = name
        self.hp = hp
        self.speed = speed

    def move(self, location):
        print('[지상 유닛 이동]')
        print(f'{self.name} : {self.speed} 방향으로 이동합니다. [속도 {self.speed}]')


class AttackUnit(Unit):
    def __init__(self, name, hp, damage, speed):
        Unit.__init__(self, name, hp, speed)
        self.damage = damage


class Flyable:
    def __init__(self, flying_speed):
        self.flying_speed = flying_speed

    def fly(self, name, location):
        print(f'{name} : {location} 방향으로 날아갑니다. [속도 {self.flying_speed}]')


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


hoverbike = AttackUnit('호버 바이크', 80, 20, 10)
spacecruiser = FlyableAttackUnit('우주 순양함', 500, 25, 3)

hoverbike.move('11시')
spacecruiser.fly(spacecruiser.name, '9시')

지상 유닛의 이동 속도를 의미하는 speed 인스턴스 변수를 Unit 클래스에 추가한다.

이동 동작은 나타내는 move() 메서드를 정의하고 공중 유닛과 구분하는 문구를 추가한다.

Unit 클래스를 변경하면 Unit 클래스를 상속받는 자식 클래스에 영향을 끼친다.

speed 인스턴스 변수를 새로 추가했으니 __init__() 생성자를 사용하는 부분은 변경해 줘야한다.

 

그러나 많은 지상 유닛과 공중 유닛을 이동할 때마다 지상 유닛인지 공중 유닛인지 구분해 move()와 fly() 메서드를 적용하는 것은 너무 번거롭다.

fly() 메서드를 사용할 때 유닛의 이름 정보까지 전달해야하는 불편함도 존재한다.

 

이런 경우 메서드 오버라이딩(메서드 재정의, method overriding)을 사용할 수 있다.

메서드 오버라이딩은 상속 관계일 때 자식 클래스에서 부모 클래스에 정의한 메서드를 그대로 사용하지 않고 같은 이름으로 메서드를 새롭게 정의해 사용하는 방법이다.

 

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):  # Unit 클래스의 move() 메서드를 오버라이딩
        print('[공중 유닛 이동]')
        self.fly(self.name, location)


hoverbike = AttackUnit('호버 바이크', 80, 20, 10)
spacecruiser = FlyableAttackUnit('우주 순양함', 500, 25, 3)

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

 

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

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

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

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

 

다중 상속을 받은 클래스에서 super()로 부모 클래스에 접근할 때는 순서상 가장 먼저 상속받은 클래스에 접근하게 된다.

그러므로 다중 상속을 할 때 모든 부모 클래스의 생성자를 호출하려면 super() 를 사용하지 않고 다음과 같이 부모 클래스의 이름을 명시해서 접근해야한다.

class FlyableUnit(Flyable,Unit):
    def __init__(self):
        Unit.__init__(self)
        Flyable.__init__(self)

 

실습 문제: 부동산 프로그램 만들기

문제 - 주어진 코드를 활용해 부동산 프로그램을 작성하세요.

조건

1. 생성자로 인스턴스 변수를 정의한다. 매물 정보를 표시하는 show_detail() 메서드에서는 인스턴스 변수를 순서대로 출력한다.

2. 실행결과에 나온 3가지 매물을 객체로 만들고 총 매물 수를 출력한 뒤 show_detail() 메서드를 호출해 각 매물 정보를 표시한다.

class House:
    # 매물 초기화: 위치, 건물 종류, 매물 종류, 가격, 준공연도
    def __init__(self, location, house_type, deal_type, price, completion_year):
        pass

    # 매물 정보 표시
    def show_detail(self):
        pass
# 실행 결과
총 3가지 매물이 있습니다.
강남 아파트 매매 10억 원 2010년
마포 오피스텔 전세 5억 원 2007년
송파 빌라 월세 500/50만 원 2000년
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(f'총 {len(houses)}가지 매물이 있습니다.')
for house in houses:
    house.show_detail()

 

실습 문제2

문제 - 주어진 코드를 활용해 주차 차량 등록 관리 프로그램을 작성하세요.

조건

1. 총 주차 가능 대수인 capacity는 객체를 생성할 때 전달받아 인스턴스 변수로 정의한다.

2. 현재 등록된 차량 수를 관리하는 count는 객체를 생성할 때 0으로 정의한다.

3. 객체를 생성할 때 등록 가능한 대수를 출력한다.

4. 차를 신규 등록하는 register() 함수를 정의한다.

5. 신규 등록 시 등록 현황을 출력한다.

6. 총 주차 가능 대수를 초과하는 경우 "더 이상 등록할 수 없습니다."라는 메시지를 출력한다.

class ParkingManager:
    def __init__(self, capacity):
        pass

    def register(self):
        pass

manager = ParkingManager(5)
for i in range(6):
    manager.register()
class ParkingManager:
    def __init__(self, capacity):
        self.capacity = capacity
        self.count = 0
        print(f'총 {capacity}대를 등록할 수 있습니다.')

    def register(self):
        if self.count >= self.capacity:
            print('더 이상 등록할 수 없습니다.')
            return
        self.count += 1
        print(f'차량 신규 등록 ({self.count}/{self.capacity})')

manager = ParkingManager(5)
for i in range(6):
    manager.register()

'Python 기초' 카테고리의 다른 글

[Python] 예외 처리  (0) 2023.04.01
[Python] 입출력 정리  (0) 2023.03.25
[Python] 함수 정리  (0) 2023.03.22
[Python] 제어문 정리  (0) 2023.03.19
[Python] 자료구조 정리  (0) 2023.03.19

댓글