Python 클래스 심화개념

2022-03-13

.

Python_studynote(20220313)

[학습자료]

패스트 캠퍼스 “초격차 패키지 : 한 번에 끝내는 파이썬 웹 개발” 강의를 공부하고 정리한 내용입니다.

** URL : https://fastcampus.co.kr/dev_online_pyweb

[학습내용]

  • 절차지향 프로그래밍 vs 객체지향 프로그래밍

일반적으로 프로그래밍을 하는 규모에 따라 절차지향 프로그래밍을 할지 객체지향 프로그래밍을 할지 결정할 수 있다.

보통은 프로그래밍 규모가 작은 경우는 절차지향 프로그래밍이 유리한 경우가 많고, 프로그래밍의 규모가 큰 프로젝트의 경우 객체 지향 프로그래밍이 유라한 경우가 많다.

(1) 절차지향 프로그래밍

기능들을 어떤 순서(절차)로 처리할 것인가에 대해 초점을 맞춘 프로그래밍

(2) 객체지향 프로그래밍

객체가 중심이 되고, 객체를 정의하고 객체간 상호작용에 초점을 맞춘 프로그래밍

  • 아래와 같이 아주 간단한 클래스 구현 예시

스타크래프트 프로브, 질럿, 드라군 객체를 생성하는 클래스를 구현해보자.

(1) 클래스에 속성을 추가해보기

class Unit:
    # __init__은 생성자라고 함
    # 객체를 생성할때 호출되는 메서드
    # __init__ 생성자의 가장 처음 파라미터는 self임
    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        print(f"{self.name}이 생성되었습니다")

# 프로브 객체 생성
# 인스턴스 = 클래스이름()
probe = Unit("프로브",20,20,5)

# 질럿 객체 생성
zealot = Unit("질럿",100,60,16)

# 드라군 객체 생성
dragoon = Unit("드라군",100,80,20)
프로브이 생성되었습니다
질럿이 생성되었습니다
드라군이 생성되었습니다

(2) 클래스에 메서드 추가해보기

class Unit:
    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        print(f"{self.name}이 생성되었습니다")
    
    # __str__은 객체를 print할때 호출되는 메서드다.
    def __str__(self):
        return f"[{self.name}] 체력:{self.hp}, 실드:{self.shield}, 공격력:{self.demage}"
        
# 프로브 객체 생성
probe = Unit("프로브",20,20,5)

# 질럿 객체 생성
zealot = Unit("질럿",100,60,16)

# 드라군 객체 생성
dragoon = Unit("드라군",100,80,20)

print(probe)
print(zealot)
print(dragoon)
프로브이 생성되었습니다
질럿이 생성되었습니다
드라군이 생성되었습니다
[프로브] 체력:20, 실드:20, 공격력:5
[질럿] 체력:100, 실드:60, 공격력:16
[드라군] 체력:100, 실드:80, 공격력:20
  • 클래스에서 속성

클래스에서 속성은 세가지가 있다. 인스턴스 속성, 클래스 속성, 비공개 속성이 있다.

(1) 인스턴스 속성

인스턴스 속성은 객체마다 다른 값을 가지는 속성을 말한다.

위에서 구현한 코드에서 아래에 init 메서드로 정의한 속성이 인스턴스 속성이다.

...

    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        print(f"{self.name}이 생성되었습니다")

...

위에서 정의한 속성에 따라 각 객체별로 속성값이 다르게 부여가 된다.

프로브 객체는 프로브 대로 체력, 쉴드, 공격력 속성이 있고, 질럿객체는 프로브와 다르게 체력, 쉴드, 공격력이 또 별도로 정의된다. 그래서 객체별로 속성값이 다르게 부여가 된다는 것이다.

참고로 헷갈릴 수 있는게 있는데 클래스 안에서 인스턴스 속성을 사용할때는 self.속성명 으로 사용하면 되고, 클래스 밖에서 인스턴스 속성을 사용할때는 객체명.속성명 으로 사용해야 한다.

(2) 클래스 속성

클래스 속성은 모든 객체가 공유하는 속성을 말한다.

예를 들어서 Unit 클래스를 통해 만들어진 객체의 갯수를 알고 싶다면 아래와 같이 count라는 클래스 속성을 정의해서 사용하면 된다.

ex)

class Unit:
    count=0
    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        print(f"{self.name}이 생성되었습니다")
        # 클래스 속성을 사용할때는 클래스명.클래스속성 형태로 사용하면 된다.
        Unit.count += 1

probe = Unit("프로브",20,20,5)
zealot = Unit("질럿",100,60,16)
dragoon = Unit("드라군",100,80,20)

# 클래스 속성을 사용할때는 클래스명.클래스속성 형태로 사용하면 된다.
print(Unit.count)
프로브이 생성되었습니다
질럿이 생성되었습니다
드라군이 생성되었습니다
3

(3) 비공개 속성

비공개 속성은 클래스 안에서만 접근이 가능한 속성을 말한다.

만약에 hp 속성을 클래스 밖에서는 접근하지 못하도록 하고 싶다면 아래와 같이 정의해주면 된다.

class Unit:
    def __init__(self, name, hp, shield, demage):
        self.name = name
        # 언더바 두개를 속성앞에다가 붙여주면 비공개 속성으로 만들 수 있다.
        self.__hp = hp
        self.shield = shield
        self.demage = demage
        print(f"{self.name}이 생성되었습니다")
    
    def __str__(self):
        return f"[{self.name}] 체력:{self.__hp}, 실드:{self.shield}, 공격력:{self.demage}"

zealot = Unit("질럿",100,60,16)
zealot.__hp = 9999
# 클래스 밖에서 신규할당을 해도 출력을 해보면 적용이 안된것을 알 수 있다.
print(zealot)
질럿이 생성되었습니다
[질럿] 체력:100, 실드:60, 공격력:16
  • 속성 실습
class Unit:
    """
    인스턴스 속성 : 이름, 체력, 쉴드, 공격력
    --> 객체마다 다른 값을 가지는 속성

    클래스 속성 : 전체 유닛 갯수
    --> 모든객체가 공유하는 속성

    비공개 속성 : 체력
    --> 클래스 안에서만 사용가능한 속성
    """

    count = 0

    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.__hp = hp
        self.shield = shield
        self.demage = demage
        Unit.count += 1
        print(f"{self.name}이 생성되었습니다")
    
    def __str__(self):
        return f"[{self.name}] 체력:{self.__hp}, 실드:{self.shield}, 공격력:{self.demage}"
        
probe = Unit("프로브",20,20,5)
zealot = Unit("질럿",100,60,16)
dragoon = Unit("드라군",100,80,20)

# 인스턴스 속성 수정
# 클래스 밖에서 인스턴스 속성에 접근할때는 아래와 같이 구현하면 된다.
# 질럿이 공격력 업그레이드가 되었다고 가정하면 아래와 같이 구현하면 된다.
zealot.demage += 1
print(zealot)

# 전체유닛 갯수 출력
print(Unit.count)

# 비공개 속성에 접근
probe.__hp = 9999
print(probe)
# 프로브를 출력해보면 체력이 변경되지 않았다.
# 사실 접근이 아예 불가능한거는 아니다.
프로브이 생성되었습니다
질럿이 생성되었습니다
드라군이 생성되었습니다
[질럿] 체력:100, 실드:60, 공격력:17
3
[프로브] 체력:20, 실드:20, 공격력:5
# 아래와 같이 네임 맹글링이라는 것을 사용하면 변경이 가능하다.
# 네임 맹글링은 이름을 복잡하게 해서 클래스 속성을 바꾸는거를 까다롭게 한 의도다.
probe._Unit__hp = 9999
print(probe)
[프로브] 체력:9999, 실드:20, 공격력:5
  • 클래스의 여러가지 메서드

클래스의 메서드에는 인스턴스 메서드, 클래스 메서드, 정적 메서드, 매직 메서드로 나누어진다.

(1) 인스턴스 메서드

인스턴스 속성에 접근할수 있는 메서드로 항상 첫번째 파라미터로 self를 갖는다.

인스턴스 메서드 구현 예시 : hit라는 인스턴스 메서드를 구현해보자

예를 들어서 질럿이 프로브를 때린다고 했을때 쉴드 먼저 깎이고 쉴드 다 깎이면 체력이 깎이는 이런 기능을 구현해보자.

class Unit:
    count = 0
    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        Unit.count += 1
        print(f"{self.name}이 생성되었습니다")
    
    def __str__(self):
        return f"[{self.name}] 체력:{self.hp}, 실드:{self.shield}, 공격력:{self.demage}"

    # 인스턴스 메서드
    def hit(self, demage):
        # 쉴드 변경
        if self.shield >= demage:
            self.shield -= demage
            demage = 0
        else:
            demage -= self.shield
            self.shield = 0

        # 체력 변경
        if demage > 0:
            if self.hp > demage:
                self.hp -= demage
            else:
                self.hp = 0

probe = Unit("프로브",20,20,5)
zealot = Unit("질럿",100,60,16)

probe.hit(zealot.demage)
print(probe)
프로브이 생성되었습니다
질럿이 생성되었습니다
[프로브] 체력:20, 실드:4, 공격력:5
probe.hit(zealot.demage)
print(probe)
[프로브] 체력:8, 실드:0, 공격력:5
probe.hit(zealot.demage)
print(probe)
[프로브] 체력:0, 실드:0, 공격력:5

(2) 클래스 메서드

클래스 속성에 접근하기 위한 메서드

클래스를 의미하는 cls 를 파라미터로 받는다.

ex)

class Unit:
    count = 0

    ...

    @classmethod
    def print_count(cls):
        print(f"전체 유닛수 : {cls.count} ")

(3) 정적 메서드

인스턴스를 만들 필요가 없는 메서드

self를 받지 않는다.

메서드가 인스턴스 유무와 상관없이 독립적으로 사용될때 쓴다.

ex)

class Math:
    @staticmethod
    def add(x,y):
        return x+y

(4) 매직 메서드

클래스 안에서 정의할 수 있는 스페셜한 메서드

특별한 상황에 호출된다.

__이름__의 형태로 되어 있다.

  • 클래스 메서드 실습
class Unit:
    count = 0
    def __init__(self, name, hp, shield, demage):
        self.name = name
        self.hp = hp
        self.shield = shield
        self.demage = demage
        Unit.count += 1
        print(f"{self.name}이 생성되었습니다")
    
    def __str__(self):
        return f"[{self.name}] 체력:{self.hp}, 실드:{self.shield}, 공격력:{self.demage}"

    # 인스턴스 메서드
    def hit(self, demage):
        # 쉴드 변경
        if self.shield >= demage:
            self.shield -= demage
            demage = 0
        else:
            demage -= self.shield
            self.shield = 0

        # 체력 변경
        if demage > 0:
            if self.hp > demage:
                self.hp -= demage
            else:
                self.hp = 0

    # 클래스 메서드
    # 클래스 메서드는 아래와 같이 데코레이터를 붙여서 구현해야 한다.
    @classmethod
    def get_count(cls):
        print(f"생성한 유닛수 : {cls.count}")

probe = Unit("프로브",20,20,5)
zealot = Unit("질럿",100,60,16)

probe.hit(zealot.demage)
print(probe)

Unit.get_count()
프로브이 생성되었습니다
질럿이 생성되었습니다
[프로브] 체력:20, 실드:4, 공격력:5
생성한 유닛수 : 2

정적 메서드 구현예시

class Math:
    # 정적 메서드 역시 아래와 같이 데코레이터를 붙여서 구현해야 한다.
    @staticmethod
    def add(x,y):
        return x+y

    @staticmethod
    def sub(x,y):
        return x-y

print(Math.add(3,4))
print(Math.sub(100,20))
7
80

매직 메서드 구현 예시

앞에서 우리는 __init__ 메서드와 __str__ 메서드를 사용해봤는데 이것들이 매직 메서드이다.

이런 메서드들은 특정한 상황에 호출이 된다.

__init__ 은 클래스에서 객체를 생성할때 호출이되고, __str__ 메서드는 객체를 출력할때 호출이 되는 메서드이다.

객체가 가지고 있는 속성과 메서드를 확인할수 있는 함수가 있다.

아래와 같이 출력하면 probe 객체의 속성과 메서들 확인할 수 있다.

print(dir(probe))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'count', 'demage', 'get_count', 'hit', 'hp', 'name', 'shield']
  • 상속 구현해보기

배틀그라운드의 아이템을 구현해보자.

스크린샷 2022-03-13 오후 3 07 24

스크린샷 2022-03-13 오후 3 07 34

class Item:
    """
    속성 : 이름
    메서드 : 줍기, 버리기
    """
    def __init__(self, name):
        self.name = name
    
    def pick(self):
        print(f"[{self.name}]을(를) 주웠습니다.")

    def discard(self):
        print(f"[{self.name}]을(를) 버렸습니다.")

class Weapon(Item):
    """
    속성 : 공격력
    메서드 : 공격하기
    """
    def __init__(self, name, demage):
        super().__init__(name)
        self.demage = demage

    def attack(self):
        print(f"[{self.name}]을(를) 이용해 {self.demage}로 공격합니다.")

class HealingItem(Item):
    """
    속성 : 회복량
    메서드 : 사용하기
    """
    def __init__(self, name, recovery_amount):
        super().__init__(name)
        self.recovery_amount = recovery_amount

    def use(self):
        print(f"[{self.name}]을(를) 사용합니다. {self.recovery_amount} 회복")

m16 = Weapon("m16", 110)
bungdae = HealingItem("붕대", 20)

m16.attack()
bungdae.use()
[m16]을(를) 이용해 110로 공격합니다.
[붕대]을(를) 사용합니다. 20 회복

추상클래스 구현해보기

추상메서드를 하나라도 갖고 있는 클래스를 추상클래스라고 한다.

추상메서드는 상속받는 자식클래스에서 구현하도록 강제하도록 메서드이다. 예를 들어서 사용하기라는 메서드를 아이템 클래스에 구현을 해놓으면 이 메서드는 반드시 상속받는 자식 클래스에서도 구현을 해야한다.

만약에 자식 클래스에서 추상 클래스를 구현하지 않는다면 실행할때 추상메서드를 구현안했다고 타입에러가 발생한다.

스크린샷 2022-03-13 오후 3 07 46

from abc import *

# 아래에 metaclass=ABCMeta 요거를 해줘야 추상클래스라는 것을 선언하는 것이다.
class Item(metaclass=ABCMeta):
    """
    속성 : 이름
    메서드 : 줍기, 버리기
    """
    def __init__(self, name):
        self.name = name
    
    def pick(self):
        print(f"[{self.name}]을(를) 주웠습니다.")

    def discard(self):
        print(f"[{self.name}]을(를) 버렸습니다.")

    # 추상메서드는 아래와 같이 데코레이터를 이용해서 구현해줘야 한다.
    @abstractmethod
    def use(self):
        pass

class Weapon(Item):
    """
    속성 : 공격력
    메서드 : 공격하기
    """
    def __init__(self, name, demage):
        super().__init__(name)
        self.demage = demage

    def use(self):
        print(f"[{self.name}]을(를) 이용해 {self.demage}로 공격합니다.")

class HealingItem(Item):
    """
    속성 : 회복량
    메서드 : 사용하기
    """
    def __init__(self, name, recovery_amount):
        super().__init__(name)
        self.recovery_amount = recovery_amount

    def use(self):
        print(f"[{self.name}]을(를) 사용합니다. {self.recovery_amount} 회복")

m16 = Weapon("m16", 110)
bungdae = HealingItem("붕대", 20)

m16.use()
bungdae.use()
[m16]을(를) 이용해 110로 공격합니다.
[붕대]을(를) 사용합니다. 20 회복