Post

파이썬 공부

파이썬 공부

들어가며

지난 글에서는 try...except 문을 이용해 프로그램 실행 중 발생하는 오류를 처리하는 예외 처리 기법을 배웠습니다. 예외 처리를 통해 우리는 예측 불가능한 상황에서도 쉽게 멈추지 않는, 훨씬 더 안정적이고 견고한 프로그램을 만들 수 있게 되었습니다.

지금까지 우리는 파이썬의 핵심 문법과 데이터 구조, 함수, 모듈 등 프로그래밍의 기본기를 착실히 다져왔습니다. 하지만 프로그램의 규모가 점점 더 커지고 복잡해진다고 상상해 봅시다. 수십, 수백 개의 함수와 변수들이 여기저기 흩어져 있다면, 코드를 이해하고 관리하기가 매우 어려워질 것입니다. 이런 코드는 흔히 ‘스파게티 코드’라고 불립니다.

이러한 복잡성을 해결하기 위해, 프로그래머들은 코드를 좀 더 체계적으로 구성하고, 현실 세계의 개념과 가깝게 모델링하는 새로운 프로그래밍 방식을 고안했습니다. 바로 객체 지향 프로그래밍(Object-Oriented Programming, OOP)입니다.

이번 글에서는 객체 지향 프로그래밍의 가장 기본적이고 핵심적인 두 가지 개념, 바로 클래스(Class)객체(Object)에 대해 알아보겠습니다.

객체 지향 프로그래밍이 왜 필요할까?

간단한 게임 캐릭터를 프로그래밍한다고 생각해 봅시다. 객체 지향 개념이 없다면 우리는 아마 이렇게 코드를 작성할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 캐릭터 1의 정보
character1_name = '전사'
character1_hp = 150
character1_power = 15

# 캐릭터 2의 정보
character2_name = '궁수'
character2_hp = 100
character2_power = 25

# 캐릭터의 동작을 정의하는 함수들
def attack(attacker_name, attacker_power, target_name, target_hp):
    print(f"{attacker_name}이(가) {target_name}을(를) 공격합니다!")
    target_hp -= attacker_power
    return target_hp

# ...

캐릭터가 몇 명 없을 때는 괜찮아 보이지만, 캐릭터가 수십, 수백 명으로 늘어난다면 어떨까요? character3_name, character4_hp… 변수 이름은 계속 길어지고, 어떤 함수가 어떤 데이터와 관련 있는지 파악하기가 점점 어려워집니다. 데이터(이름, 체력)와 그 데이터를 사용하는 동작(공격 함수)이 서로 분리되어 있기 때문입니다.

객체 지향 프로그래밍은 바로 이 문제에 대한 해답을 제시합니다. 서로 연관 있는 데이터와 그 데이터를 처리하는 함수(동작)를 하나의 덩어리로 묶어서 관리하는 것입니다. 이 덩어리가 바로 객체(Object)입니다.

클래스(Class): 객체를 만드는 설계도

객체를 만들기 위해서는 먼저, 그 객체가 어떤 데이터와 어떤 동작을 가질지 정의하는 ‘설계도’가 필요합니다. 이 설계도의 역할을 하는 것이 바로 클래스(Class)입니다.

클래스는 붕어빵을 만드는 ‘틀’에 비유할 수 있습니다. 붕어빵 틀(클래스)은 붕어빵의 모양, 그리고 어떤 재료가 들어갈지를 정의합니다. 이 틀을 이용해 우리는 수많은 붕어빵(객체)을 찍어낼 수 있습니다.

파이썬에서는 class 키워드를 사용해 클래스를 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Character:
    # __init__ 메소드: 객체가 처음 생성될 때 호출되는 특별한 함수 (생성자)
    def __init__(self, name, hp, power):
        # self.변수명 = 값: 객체 고유의 데이터를 저장하는 공간 (속성, Attribute)
        print(f"{name} 캐릭터가 생성되었습니다!")
        self.name = name
        self.hp = hp
        self.power = power

    # 클래스 내에 정의된 함수 (메소드, Method)
    def attack(self, target):
        print(f"{self.name}이(가) {target.name}을(를) 공격합니다! (공격력: {self.power})")
        target.hp -= self.power

    def show_status(self):
        print(f"이름: {self.name}, HP: {self.hp}")
  • __init__ (생성자): Character(...)와 같이 객체를 만들 때 자동으로 호출되는 특별한 함수입니다. 객체가 생성될 때 필요한 초기 데이터(이름, 체력 등)를 받아 자신만의 속성으로 저장하는 역할을 합니다.
  • 속성(Attribute): self.name, self.hp처럼 객체가 가질 고유한 데이터를 말합니다. 각 객체는 자신만의 name, hp 값을 가집니다.
  • 메소드(Method): attack, show_status처럼 클래스 안에 정의된 함수를 말합니다. 이 객체가 수행할 수 있는 동작을 정의합니다.

객체(Object): 설계도로부터 만들어진 실체

클래스라는 설계도가 준비되었다면, 이제 이 설계도를 바탕으로 실제 ‘캐릭터’들을 만들어낼 수 있습니다. 클래스로부터 만들어진 각각의 실체를 객체(Object) 또는 인스턴스(Instance)라고 부릅니다.

붕어빵 틀(클래스)로 찍어낸 각각의 붕어빵(객체)들은 모두 붕어빵의 형태를 하고 있지만, 안에 든 팥의 양이나 구워진 정도가 조금씩 다를 수 있는 것과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Character 클래스(설계도)를 이용해 warrior와 archer라는 두 개의 객체(실체)를 생성
warrior = Character('전사', 150, 15)
archer = Character('궁수', 100, 25)

# 객체의 메소드와 속성을 사용
warrior.show_status() # 출력: 이름: 전사, HP: 150
archer.show_status()  # 출력: 이름: 궁수, HP: 100

print("\n--- 전투 시작 ---")
warrior.attack(archer) # 전사 객체의 attack 메소드 호출
archer.show_status()   # 출력: 이름: 궁수, HP: 75

archer.attack(warrior) # 궁수 객체의 attack 메소드 호출
warrior.show_status()  # 출력: 이름: 전사, HP: 125

warriorarcher는 모두 Character 클래스로부터 만들어졌지만, 각각 '전사', 150'궁수', 100이라는 자신만의 독립적인 데이터를 가지고 있습니다. warrior.attack(archer)를 호출하면 warrior의 공격력으로 archer의 체력을 감소시키는 동작이 수행됩니다. 데이터와 동작이 하나의 객체 안에 깔끔하게 캡슐화된 것입니다.

가장 중요한 약속, self

클래스 안의 메소드들이 항상 첫 번째 인자로 받는 self는 대체 무엇일까요? self메소드를 호출한 객체 자신을 가리키는 특별한 약속입니다.

warrior.attack(archer) 코드가 실행될 때, 파이썬은 attack 메소드를 호출하면서 첫 번째 인자로 warrior 객체 자신을 몰래 넘겨줍니다. 따라서 attack 메소드 내부에서 selfwarrior가 되고, self.namewarrior.name을 의미하게 되는 것입니다. 이 self를 통해 메소드는 “나 자신의” 속성에 접근할 수 있게 됩니다.

마무리하며

이번 글에서는 객체 지향 프로그래밍의 첫걸음인 클래스와 객체에 대해 배웠습니다. 클래스는 객체를 만들기 위한 ‘설계도’이며, 연관된 데이터(속성)와 행동(메소드)을 하나로 묶어 정의합니다. 객체는 이 클래스라는 설계도를 바탕으로 만들어진 ‘실체’이며, 자신만의 고유한 데이터를 가집니다.

이러한 접근 방식은 복잡한 프로그램을 논리적인 부품(객체)들의 조합으로 만들어나갈 수 있게 해줍니다. 코드는 훨씬 더 체계적으로 정리되고, 재사용하기 쉬워지며, 이해하기도 편해집니다.

이제 우리는 우리만의 데이터 타입을 직접 설계하고 만들 수 있게 되었습니다. 하지만 만약 기존에 만들어 둔 클래스의 특징을 그대로 물려받으면서, 약간의 기능을 추가하거나 변경하여 새로운 클래스를 만들고 싶다면 어떻게 해야 할까요?

다음 글에서는 객체 지향 프로그래밍의 또 다른 강력한 기둥인 상속(Inheritance)에 대해 알아보겠습니다.

This post is licensed under CC BY 4.0 by the author.