파이썬 클래스
- 2020. 12. 8. 01:18
파이썬 class
클래스를 사용하는 이유
클래스의 개념은 OOPL로 객체지향 프로그래밍 언어를 지칭하며 클래스는 데이터와 기능을 함께 묶는 방법을 제공합니다. 파이썬의 클래스에 대한 개념을 자바의 설명을 그대로 가져와 설명한다면 파이썬의 클래스는 그다지 매력적이지 않았을 것입니다.
다른 언어의 클래스를 접해본 분이라면 아마 클래스 설명할 때 추상적이 어쩌고 다형성이 어쩌고 하는데 그냥 싹 잊고 파이썬 클래스를 접하는 것이 좋습니다. 다만 객체나 매서드 등과 같은 용어는 비슷하게 사용합니다.
절차적 언어에서 사용하던 용어는 클래스 개념으로 들어오면서 변경됩니다.
함수 = 메서드
변수 = 객체
메서드 + 객체 = 클래스
클래스 할당 = 인스턴스
정도만 기억하시면 좋겠습니다.
그렇다면 궁극적으로 왜 클래스를 사용하는 것일까요? 절차적인 형태로 프로그래밍을 하여 단일적인 처리만 한다면 전혀 문제 될 것이 없습니다. 예를 들어보면 웹 서비스 프로그램을 제작했다고 했을 때 절차적인 형태의 프로그램이었다면 단 한 사람만 정상적인 서비스를 받을 수 있습니다. 만약 여러 사람이 서비스를 이용하려 한다면? 서로의 정보들이 꼬여 서비스를 받을 수 없습니다. 정보들이 꼬인다는 말은 프로그램에서 서비스를 이용하고자 하는 사람들 마다 고유한 정보들을 변수에 저장하게 될 텐데 그 변수를 이용하고자 하는 모든 사람들이 공유하기 때문입니다.
공유하기 때문에 더 좋은 것이 아니냐?라고 말할 수도 있지만 이 공유는 의도하지도 않은 원하지도 않은 공유입니다. 다시 말해 user_id 변수 값에 접속한 사람의 id가 입력되어야 한다고 했을 때 접속한 사람이 A, B, C가 있다면 모두 각각의 고유 id값으로 user_id값이 달라야 합니다. 당연한 이야기겠지요? 하지만 절차적으로 작성한 프로그램은 접속한 사람들이 A, B, C순으로 접속했다고 할 때 가장 마지막에 접속한 C의 id가 user_id 변수에 저장이 되며 A, B 또한 C의 user_id 변수를 공유하기 때문에 A와 B의 user_id값은 C의 id값이 됩니다. 좀 문제가 많죠? 많은 정도가 아니라 뭔가 잘못되었죠?
간혹 이렇게 설명했음에도 가끔 이런 질문을 받아 봅니다. 그럼 각 사용자별로 프로세스를 생성해 주면 되지 않을까요? 이 질문에 대한 대답은 반은 맞고 반은 틀립니다. 아주 틀리다고는 말을 할 수 없지만 상황에 따라서는 맞는 소리가 될 수도 있습니다. 하지만 대부분의 경우에는 각 사용자별로 프로세스를 생성하면 공유해야 하는 데이터가 있을 텐데 어떻게 통신할 것이며 데이터 싱크는 어떻게 맞춰주고 자원 할당 등등등... 신경 써야 할 것들이 많아져 머리가 아파 오기 시작합니다.
이런 문제를 단 한방에 해결해 줄 수 있는 것이 바로 클래스입니다.
파이썬 클래스 예제 코드
클래스에 대해 어떻게 하면 이해하기 쉽게 설명할 수 있을까?라고 생각하다가 개념을 하나하나 설명하기보다는 기본적인 개념이 들어가 있는 간단한 예제 코드를 하나 두고 하나하나 설명하는 것이 좋겠다는 생각이 들었습니다. 그래서 필자가 비록 게임 개발자는 아니지만 클래스를 설명하는 데 있어 누구나 공감하고 이해하기 쉬울 것 같은 생각에 화려한 그래픽이 펼쳐지지는 않지만 그래도 게임에서 이런 식으로 클래스가 동작하겠구나 라고 상상의 나래를 펼쳐 보는 것도 좋을 것 같습니다.
처음 클래스를 접하는 사람이 이 코드를 본다면 정말 난 애 할 것 같다는 생각이 듭니다. 사실 첫 술에 배부를 수는 없겠죠? 이 예제가 개념을 설명하기 위해 억지로 욱여넣은 것도 없지 않아 있습니다. 비록 잘 짜인 예제 코드는 아니지만 클래스에 대해 감을 익히는 데는 충분하리라 생각됩니다.
우선 예제 코드는 나의 캐릭터를 생성하고 몬스터가 3마리 등장하며 캐릭터는 몬스터 1마리를 처치합니다. 하지만 이내 2마리의 몬스터에게 공격을 받아 hp가 감소하는 내용입니다. 나머지 2마리도 처리할까 했지만 그다지 의미 있다고 생각하지 않아 중간에 그만두었습니다. 일단 생성한 클래스는 3개의 클래스로 간단히 구성하였습니다.
Common 클래스에서는 캐릭터 이던지 몬스터 이던지 hp와 공격력 그리고 방어력을 갖고 있기에 Common 부분에 객체를 생성하였고 공격을 하거나 대미지 입는 프로세스 또한 몬스터나 캐릭터와 동일하기에 해당 기능을 수행하는 메서드를 각각 두었습니다.
캐릭터를 담당하는 Charctor 클래스에서는 Common 클래스의 기본 내용을 모두 가져와야 하므로 Common을 상속받았고 캐릭터만이 갖고 있는 정보가 있기에 추가 작업을 하였습니다.
마찬가지로 몬스터를 담당하는 Monster 클래스에 Common 클래스를 상속받았습니다.
그럼 일단 코드를 확인해 보세요. 자세한 설명은 코드 밑에서 계속하겠습니다.
class Common:
def __init__(self, hp:int, attack:int, defence:int):
self.hp = hp
self.attack = attack
self.defence = defence
def attack_punch(self):
print("attack~!!!!")
return self.attack
def damage(self, dmg:int):
print(" damage = " + str(dmg))
self.hp -= dmg
print(" Remaining hp = " + str(self.hp))
class Charactor(Common):
def __init__(self, name:str, hp:int, mana:int, attack:int, defence:int):
super().__init__(hp, attack, defence)
self.name = name
self.mana = mana
def __call__(self):
print(f"Charactor = name : {self.name} | hp : {str(self.hp)} | ", end="")
print(f"nama : {str(self.mana)} | attack : {str(self.attack)} | defence : {str(self.defence)}")
def move(self):
print("charactor move move")
class Monster(Common):
monster_count = 0
def __init__(self):
super().__init__(10, 10, 1)
Monster.monster_count += 1
print("(Alive) Monster Count = " + str(Monster.monster_count))
def __call__(self):
print("Monster = " + str(self.hp) + "|"
+ str(self.mana)+ "|"
+ str(self.attack)+ "|"
+ str(self.defence))
def move(self):
print("monster move")
def __del__(self):
Monster.monster_count -= 1
print("(Dead) Monster Count = " + str(Monster.monster_count))
print("--캐릭터 생성")
user_1 = Charactor('CaptainBIN', 100, 100, 5, 50)
user_1()
print("--나쁜놈 등장")
mon_1 = Monster()
mon_2 = Monster()
mon_3 = Monster()
print("--mon_1 한놈만 팬다. (두번 공격)")
mon_1.damage(user_1.attack_punch())
mon_1.damage(user_1.attack_punch())
del mon_1
print("--두놈한테 맞는다. hp 짱짱")
user_1.damage(mon_2.attack_punch())
user_1.damage(mon_3.attack_punch())
# 결과
--캐릭터 생성
Charactor = | name : CaptainBIN | hp : 100 | nama : 100 | attack : 5 | defence : 50
--나쁜놈 등장
(Alive) Monster Count = 1
(Alive) Monster Count = 2
(Alive) Monster Count = 3
--mon_1 한놈만 팬다. (두번 공격)
attack~!!!!
damage = 5
Remaining hp = 5
attack~!!!!
damage = 5
Remaining hp = 0
(Dead) Monster Count = 2
--두놈한테 맞는다. hp 짱짱
attack~!!!!
damage = 10
Remaining hp = 90
attack~!!!!
damage = 10
Remaining hp = 80
(Dead) Monster Count = 1
(Dead) Monster Count = 0
파이썬 class 선언
함수를 만드는데 def를 사용하는 것처럼 파이썬에서 클래스는 class 키워드를 사용합니다. class는 메서드와 객체를 포함하고 있습니다. 메서드와 객체는 위에서 함수와 변수라는 것을 말씀드렸습니다. 정말 단순히 생각해서 class는 여러 개의 함수(메서드)와 객체(변수)를 묶어주는 꾸러미입니다.
self란 녀석은?
class를 선언한 곳에 보면 심심치 않게 보이는 self라는 녀석이 보입니다. 이 self인자는 메서드 선언 시 객체 선언 시 항상 붙어 다니는 녀석입니다. 이 self값으로 해당 메서드를 호출한 호출자를 식별할 수 있기 때문입니다. 만약 이 self가 없다면 위에서 설명한 절차적 언어와 다를 바가 없습니다.
혹시 위의 예제 코드에서 class Monster를 선언한 곳에서 self가 붙어 있지 않은 객체를 보셨나요? monster_count입니다. 변수 명에서도 알 수 있듯이 몬스터 개수를 파악하는 데 사용하기 위해 누구나 접근 가능하도록 self 없이 객체를 선언하였습니다.
특수 메서드
파이썬 class에서 특수 메서드가 있습니다. 바로 언더바 두 개가 붙은 형태로 말이죠. 위의 예제를 보면 대표적인 것이 __init__입니다. 특수 메서드는 위에서 사용한 것 이외에도 많이 있습니다. 하지만 대체로 위에서 사용한 특수 메서드를 많이 사용합니다.
__init__ 특수 메서드는 클래스가 생성될 때마다 수행되는 부분입니다. 고로 초기화 작업을 해주는데 유용하게 사용할 수 있습니다.
__call__ 특수 메서드는 객체를 호출할 때 실행되는 메서드입니다. 위의 예제에서 캐릭터를 능력치를 파라미터로 전달하면서 객체를 만들고 바로 user_1()으로 객체를 호출하여 입력한 능력치를 print 한 것을 확인할 수 있습니다. 즉, 클래스 만의 고유한 기능을 수행하는데 메서드 이름을 무엇으로 할지 굳이 고민할 필요도 없이 __call__ 특수 메서드를 사용하면 편리합니다.
__del__ 특수 메서드는 객체가 삭제될 때 수행되는 메서드입니다. 위의 예제에서 캐릭터에 죽은 몬스터 1이 제거되면서 monster_count를 1 감소시키는 것을 확인할 수 있습니다.
super()
위의 예제에서 특수 메서드 __init__ 부분에 사용한 super()가 보이시나요? 3개의 class마다 __init__이 존재합니다. 만약 super()를 사용하지 않는다면 Common 클래스를 캐릭터 클래스와 몬스터 클래스에서 상속받았다 하더라도 메서드 명이 동일하여 부모의 Common의 메서드는 무시된 상태로 캐릭터 클래스와 몬스터 클래스의 __init__만을 허용할 것입니다. 이때 사용할 수 있는 것이 바로 super()입니다. 즉, 겹치는 __init__메서드에 대해서 부모인 __init__을 사용할 수 있습니다.
이상으로 파이썬 class에 대한 포스팅을 마치겠습니다. 뭔가 장황한 듯 포스팅 글을 작성하긴 했지만 알고 나면 정말 별거 없습니다. 클래스 개념도 그렇게 어려운 개념이 아닙니다. 혹시 위의 예제 코드와 설명을 보고서도 이해 안 가는 부분이 있다면 언제든 댓글 환영합니다. ^^