Skip to main content

지금까지 우리는 변수, 조건문, 반복문, 함수, 파일, 모듈까지 배웠습니다. 여기까지 오면 이런 생각이 들기 시작합니다.

"작은 프로그램은 만들겠는데, 코드가 길어지면 어디서부터 정리해야 하지?"

바로 이 지점에서 클래스와 객체가 등장합니다. 클래스는 복잡해지는 프로그램을 더 알아보기 쉽게 정리해 주는 도구입니다. 관련 있는 데이터와 기능을 한 덩어리로 묶어 주기 때문에, 프로그램이 커질수록 진가를 발휘합니다.12

이번 장에서는 객체지향이라는 말을 너무 어렵게 생각하지 않고, "관련 있는 정보와 행동을 함께 묶는 방법" 정도로 자연스럽게 받아들이면 충분합니다. 처음부터 철학처럼 외울 필요는 없습니다. 손으로 코드를 써 보면 감이 옵니다.

클래스와 객체로 더 큰 프로그램 설계하기

왜 클래스가 필요할까

함수만으로도 꽤 많은 프로그램을 만들 수 있습니다. 하지만 프로그램이 커지면 데이터와 기능이 여기저기 흩어지기 쉽습니다.

예를 들어 게임 캐릭터를 만든다고 해 봅시다. 이름, 체력, 공격력 같은 정보가 필요하고, 공격하기, 회복하기, 상태 보기 같은 기능도 필요합니다. 이걸 전부 따로따로 변수와 함수로만 관리하면, 캐릭터가 두 명만 되어도 코드가 금방 복잡해집니다.

name1 = "용사"
hp1 = 100
attack1 = 15

name2 = "슬라임"
hp2 = 30
attack2 = 5

이런 식으로 시작하면 처음엔 쉬워 보이지만, 캐릭터가 10명, 100명이 되면 정신이 아득해집니다. 누구의 체력인지, 어떤 함수가 누구를 바꾸는지 계속 추적해야 하기 때문입니다.

클래스는 이런 문제를 해결하려고 등장합니다. 캐릭터라는 설계도를 한 번 만들고, 그 설계도로 용사도 만들고 슬라임도 만들 수 있습니다. 이렇게 하면 프로그램이 훨씬 정돈됩니다.3

클래스와 객체를 한 번에 이해하기

클래스는 설계도이고, 객체는 그 설계도로 만든 실제 대상입니다.45

붕어빵 틀을 떠올려 보면 쉽습니다.

  • 클래스는 붕어빵 틀입니다.

  • 객체는 실제로 구워진 붕어빵입니다.

  • 같은 틀로 여러 붕어빵을 만들 수 있지만, 각각은 따로 존재합니다.

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

class Dog:
    pass

아직 내용이 없는 아주 단순한 클래스입니다. pass는 "일단 비워 두겠다"는 뜻입니다.4

이제 이 클래스로 객체를 만들어 봅시다.

class Dog:
    pass

my_dog = Dog()
print(my_dog)

Dog()처럼 클래스 이름 뒤에 괄호를 붙이면 객체가 만들어집니다. 이 과정을 인스턴스 생성이라고도 합니다. 여기서 my_dog는 Dog 클래스의 객체, 더 정확히는 인스턴스입니다.5

속성과 메서드: 객체는 데이터와 행동을 함께 가진다

객체는 보통 두 가지를 가집니다.

하나는 속성(attribute) 입니다. 객체가 가지고 있는 정보입니다. 예를 들어 강아지의 이름, 나이, 색깔 같은 것이 속성입니다.

다른 하나는 메서드(method) 입니다. 객체가 할 수 있는 행동입니다. 예를 들어 짖기, 먹기, 뛰기 같은 것이 메서드입니다.6

코드로 보면 더 분명합니다.

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def bark(self):
        print(f"{self.name}가 멍멍 짖습니다!")

이 클래스에는 두 가지 속성이 있습니다.

self.name은 이름이고, self.age는 나이입니다.

그리고 bark()라는 메서드가 있습니다. 이 메서드는 강아지가 짖는 행동을 표현합니다.

객체를 만들어 사용해 보면 이렇습니다.

dog1 = Dog("초코", 3)
dog2 = Dog("보리", 5)

print(dog1.name)
print(dog2.age)

dog1.bark()
dog2.bark()

출력 결과는 대략 이런 모습입니다.

초코
5
초코가 멍멍 짖습니다!
보리가 멍멍 짖습니다!

같은 클래스에서 만들었지만, dog1과 dog2는 서로 다른 객체입니다. 그래서 각각 자기만의 이름과 나이를 가질 수 있습니다.5

__init__()와 self는 무엇일까

처음 클래스를 배우면 가장 낯선 것이 __init__()와 self입니다. 이름부터 심상치 않아서 겁먹기 쉽지만, 뜻은 단순합니다.

__init__()는 객체가 만들어질 때 자동으로 실행되는 특별한 메서드입니다. 보통 객체의 시작 상태를 정할 때 사용합니다.35

class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

Student("민지", 1)처럼 객체를 만들면, 파이썬은 __init__()를 자동으로 호출합니다. 그래서 이름과 학년이 객체 안에 저장됩니다.

그렇다면 self는 무엇일까요?

self는 바로 지금 다루고 있는 그 객체 자신을 가리킵니다.57

예를 들어 아래 코드에서:

class Student:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print(f"안녕하세요, 저는 {self.name}입니다.")

student1.say_hello()를 호출하면, self는 student1을 뜻합니다. student2.say_hello()를 호출하면, self는 student2를 뜻합니다.

즉, self 덕분에 같은 메서드라도 각 객체의 자기 정보를 사용할 수 있습니다.

초보자가 가장 많이 하는 질문 중 하나는 이것입니다.

"왜 메서드 정의에는 self가 있는데, 호출할 때는 안 쓰나요?"

답은 이렇습니다. 객체가 메서드를 호출할 때 파이썬이 알아서 첫 번째 자리에 그 객체를 넣어 주기 때문입니다.5

student1.say_hello()

이 코드는 개념적으로는 아래와 비슷하게 동작합니다.

Student.say_hello(student1)

직접 이렇게 쓸 일은 거의 없지만, 원리를 이해하는 데는 도움이 됩니다.

첫 번째 실전 예제: 게임 캐릭터 만들기

이제 클래스가 왜 편한지 체감할 수 있는 예제를 만들어 봅시다.

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

    def show_status(self):
        print(f"이름: {self.name}, 체력: {self.hp}, 공격력: {self.attack}")

    def attack_target(self, target):
        target.hp -= self.attack
        print(f"{self.name}이(가) {target.name}을(를) 공격했습니다!")

hero = Character("용사", 100, 20)
slime = Character("슬라임", 30, 5)

hero.show_status()
slime.show_status()

hero.attack_target(slime)
slime.show_status()

이 코드는 꽤 재미있는 사실을 보여 줍니다.

hero도 Character 객체이고, slime도 Character 객체입니다. 둘은 같은 설계도로 만들어졌지만 상태는 다릅니다. 그리고 한 객체의 메서드가 다른 객체를 받아서 상태를 바꾸기도 합니다.

이게 바로 클래스가 큰 프로그램에서 강력한 이유입니다. "무엇을 할 수 있는지"와 "어떤 상태를 가지고 있는지"를 하나의 대상 안에 담을 수 있기 때문입니다.1

인스턴스 속성과 클래스 속성

클래스 안의 속성은 크게 두 종류로 나눠 생각하면 편합니다.

하나는 인스턴스 속성입니다. 각 객체가 따로 가지는 값입니다. 보통 __init__() 안에서 self.이름 형태로 만듭니다.

다른 하나는 클래스 속성입니다. 클래스 전체가 함께 공유하는 값입니다.67

예제를 봅시다.

class Student:
    school_name = "파이썬 초등학교"

    def __init__(self, name):
        self.name = name

여기서 school_name은 클래스 속성입니다. 모든 학생이 같은 학교에 다닌다는 뜻으로 쓸 수 있습니다.

s1 = Student("지우")
s2 = Student("하준")

print(s1.name)
print(s2.name)
print(Student.school_name)
print(s1.school_name)
print(s2.school_name)

name은 객체마다 다르지만, school_name은 모두가 공유합니다.

그런데 여기서 한 가지 조심할 점이 있습니다. 객체를 통해 같은 이름의 값을 대입하면, 클래스 속성을 바꾸는 것이 아니라 그 객체만의 인스턴스 속성이 새로 생길 수 있습니다.7

class Dog:
    sound = "멍멍"


dog1 = Dog()
dog2 = Dog()

dog1.sound = "왈왈"

print(dog1.sound)   # 왈왈
print(dog2.sound)   # 멍멍
print(Dog.sound)    # 멍멍

이 코드는 "공유값을 바꿨다"가 아니라, dog1만 다른 값을 갖게 된 예입니다. 초보자에게는 조금 헷갈리는 부분이니 천천히 눈에 익혀 두면 좋습니다.

메서드는 함수를 객체 안에 넣은 것과 비슷하다

우리는 이미 함수를 배웠습니다. 메서드는 클래스 안에 들어 있는 함수라고 생각하면 이해하기 쉽습니다.6

예를 들어 다음 두 코드를 비교해 봅시다.

함수 방식입니다.

def introduce(name, age):
    print(f"이름은 {name}, 나이는 {age}살입니다.")

introduce("수아", 10)

클래스 방식입니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"이름은 {self.name}, 나이는 {self.age}살입니다.")

p = Person("수아", 10)
p.introduce()

둘 다 비슷한 결과를 내지만, 클래스 방식은 사람 하나를 하나의 객체로 다룰 수 있습니다. 그래서 사람이 많아져도 정리가 쉽습니다.

객체의 상태를 메서드로 바꾸기

클래스의 진짜 재미는 메서드가 객체의 상태를 바꾸는 데 있습니다.

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}원을 입금했습니다.")
        else:
            print("입금 금액은 0보다 커야 합니다.")

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}원을 출금했습니다.")
        else:
            print("출금할 수 없습니다.")

    def show_balance(self):
        print(f"현재 잔액: {self.balance}원")

사용 예시는 이렇습니다.

account = BankAccount("김파이", 10000)
account.show_balance()
account.deposit(5000)
account.withdraw(3000)
account.show_balance()

이 클래스의 장점은 명확합니다. 잔액이라는 데이터를 아무 데서나 막 바꾸는 대신, 입금과 출금이라는 메서드를 통해 더 안전하게 다룰 수 있습니다. 이런 생각이 객체지향의 중요한 출발점입니다. 관련 데이터와 조작 방법을 한곳에 모아 두는 것이죠.8

출력이 더 읽기 좋게: __str__()

객체를 그냥 출력하면 종종 이런 식으로 나옵니다.

<__main__.Dog object at 0x...>

파이썬 입장에서는 맞는 말이지만, 사람 눈에는 그다지 친절하지 않습니다. 이럴 때 __str__() 메서드를 정의하면, print(객체) 했을 때 보기 좋은 문자열을 보여 줄 수 있습니다.7

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"{self.title} / {self.author}"

book1 = Book("파이썬 입문", "홍길동")
print(book1)

출력:

파이썬 입문 / 홍길동

이 기능은 디버깅할 때도 유용하고, 사용자에게 보기 좋은 결과를 보여 줄 때도 좋습니다.

조금만 더: 상속은 기존 설계도를 이어받는 것

클래스를 배우다 보면 상속(inheritance) 이라는 말을 만나게 됩니다. 이름이 조금 무겁지만 뜻은 간단합니다. 기존 클래스를 바탕으로 새 클래스를 만드는 방법입니다.19

예를 들어 동물을 공통으로 표현하는 클래스가 있다고 해 봅시다.

class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name}가 먹습니다.")

이제 개 클래스를 만들 수 있습니다.

class Dog(Animal):
    def bark(self):
        print(f"{self.name}가 멍멍 짖습니다.")

사용해 보면 이렇습니다.

dog = Dog("초코")
dog.eat()
dog.bark()

Dog는 자기만의 bark()도 가지고 있고, 부모 클래스 Animal의 eat()도 그대로 사용할 수 있습니다. 즉, 공통 기능은 재사용하고, 필요한 기능만 추가하면 됩니다.9

초보자 단계에서는 상속을 깊게 파고들 필요는 없습니다. "비슷한 객체들 사이의 공통점을 묶고 싶을 때 쓰는구나" 정도만 이해해도 충분합니다.

객체지향의 핵심 감각: 무엇을 중심으로 생각할까

함수 중심으로 코드를 짤 때는 보통 "무슨 일을 해야 하지?"부터 생각합니다.

객체 중심으로 코드를 짤 때는 한 걸음 달라집니다.

"이 프로그램에는 어떤 대상이 등장하지?"를 먼저 떠올립니다.

예를 들어 도서 관리 프로그램이라면 책, 회원, 대출기록이 있을 수 있습니다. 학생 관리 프로그램이라면 학생, 과목, 성적이 있을 수 있습니다. 게임이라면 캐릭터, 아이템, 몬스터가 있을 수 있습니다.

이렇게 대상을 먼저 찾고, 각 대상이 가지는 정보와 행동을 정리하면 클래스를 설계하기 쉬워집니다.8

간단히 정리하면 다음 순서가 좋습니다.

  • 프로그램에 등장하는 대상을 찾는다.

  • 각 대상이 가져야 할 정보를 정한다.

  • 각 대상이 할 수 있는 행동을 정한다.

  • 정보는 속성으로, 행동은 메서드로 옮긴다.

이 사고방식은 프로그램이 커질수록 큰 힘을 발휘합니다.

초보자가 자주 하는 실수

클래스를 처음 배울 때는 비슷한 실수를 많이 합니다. 미리 알고 있으면 덜 헷갈립니다.

self를 빼먹는 실수

class Cat:
    def __init__(name):
        self.name = name

이 코드는 잘못되었습니다. 메서드의 첫 번째 매개변수로 self가 있어야 합니다.

올바른 코드는 다음과 같습니다.

class Cat:
    def __init__(self, name):
        self.name = name

속성과 지역 변수를 헷갈리는 실수

class Cat:
    def __init__(self, name):
        name = name

이렇게 쓰면 객체에 이름이 저장되지 않습니다. 왼쪽도 그냥 지역 변수 name일 뿐입니다.

반드시 객체에 저장하려면 self.name으로 써야 합니다.

class Cat:
    def __init__(self, name):
        self.name = name

메서드 호출에 괄호를 빼먹는 실수

cat = Cat("나비")
cat.say_name

이렇게 쓰면 메서드를 실행한 것이 아니라, 메서드 자체를 가리키는 상태가 됩니다. 실행하려면 괄호를 붙여야 합니다.

cat.say_name()

클래스 속성과 인스턴스 속성을 섞어 쓰는 실수

공유해야 하는 값인지, 객체마다 달라야 하는 값인지 구분하는 습관이 중요합니다. 학생마다 이름은 달라야 하니 인스턴스 속성이 맞고, 학교 이름처럼 모두 같아야 하는 값은 클래스 속성이 어울립니다.6

모든 걸 클래스부터 만들려는 실수

클래스는 강력하지만 만능 열쇠는 아닙니다. 아주 짧고 단순한 프로그램은 함수와 기본 자료형만으로도 충분합니다. 클래스는 "관련 있는 데이터와 기능을 묶으면 더 좋아질 때" 사용하면 됩니다.1

실전 감각을 키우는 연습 문제

다음 같은 클래스를 직접 만들어 보면 감이 빨리 잡힙니다.

  • Book: 제목, 저자, 가격 / 책 정보 출력하기

  • Student: 이름, 국어점수, 영어점수 / 평균 구하기

  • Timer: 시작 상태, 종료 상태 / 시작하기, 멈추기

  • Monster: 이름, 체력 / 공격받기, 살아있는지 확인하기

중요한 것은 멋진 클래스를 만드는 것이 아니라, "정보와 행동을 함께 묶는다"는 감각을 몸에 익히는 것입니다.

마무리

이번 장에서 배운 핵심은 이것입니다.

클래스는 더 큰 프로그램을 정리하기 위한 설계도입니다. 객체는 그 설계도로 만든 실제 대상입니다. 객체는 속성으로 상태를 가지고, 메서드로 행동합니다. __init__()는 객체가 처음 만들어질 때 상태를 준비하고, self는 지금 다루는 객체 자신을 가리킵니다. 또 인스턴스 속성과 클래스 속성의 차이를 알면, 무엇이 객체마다 달라져야 하고 무엇이 함께 공유되어야 하는지 구분할 수 있습니다.367

처음에는 클래스가 함수보다 더 복잡해 보일 수 있습니다. 하지만 한 번 감을 잡으면 오히려 코드를 더 자연스럽게 읽게 됩니다. "이 데이터는 누구의 것인가?", "이 행동은 누가 해야 하는가?"를 분명하게 만들기 때문입니다.

다음 장에서는 지금까지 배운 내용을 바탕으로 작은 프로젝트를 만들면서, 파이썬을 진짜 내 것으로 만들어 볼 것입니다. 이제 변수와 함수만 아는 단계에서 한 걸음 더 나아가, 스스로 구조를 설계하는 개발자의 시야를 갖게 된 것입니다.

참고

1Python Classes: The Power of Object-Oriented Programming

2Object-Oriented Programming (OOP) in Python

39. Classes — Python 3.14.3 documentation

4Python Classes

5Python Classes and Objects (With Examples)

6Attributes and Methods in Python

7Python Classes and Objects: A Beginner's Guide (2026)

811.1 Object-oriented programming basics - Introduction to Python Programming | OpenStax

9Python Object-Oriented Programming (With Examples)

책 목록

처음부터 실전까지, 진짜 쉬운 파이썬 교과서

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기