본문 바로가기
자기 개발/Python

파이썬(Python) 공부 10편 — 클래스(Class)와 객체지향 기초 완전 정복 | __init__·상속·캡슐화·매직 메서드까지

by conrad 2026. 4. 1.
10 / 15 Python 공부 시리즈 — 클래스(Class)와 객체지향
← 9편: 예외 처리 try/except 완전 정복 보러 가기
Python 공부 시리즈 · 10편 | class · __init__ · 상속 · 캡슐화 · 매직 메서드

파이썬(Python) 공부 10편
클래스(Class)와 객체지향 기초 완전 정복

지금까지 함수·리스트·딕셔너리를 배웠다면, 이제는 이것들을 하나로 묶어 구조화하는 방법을 익힐 차례입니다. 객체지향 프로그래밍(OOP)은 현실의 사물을 코드로 표현하는 방법론입니다. 클래스 정의부터 생성자·상속·캡슐화·매직 메서드까지 핵심 개념을 한 번에 정리합니다.

클래스 / 인스턴스 __init__ 생성자 인스턴스·클래스 변수 상속 / super() 캡슐화 매직 메서드

💡 클래스(Class)란 — 설계도와 실제 물건

객체지향 프로그래밍에서 클래스(Class)는 설계도이고, 인스턴스(Instance)는 그 설계도로 만들어진 실제 물건입니다. 붕어빵 틀이 클래스라면, 틀로 찍어낸 붕어빵 하나하나가 인스턴스입니다.

Class 클래스 — 설계도

속성(데이터)과 메서드(동작)를 하나로 묶은 틀. 한 번 정의하면 인스턴스를 여러 개 만들 수 있다.

예: Dog 클래스 → 모든 개의 공통 특성 정의

Instance 인스턴스 — 실제 물건

클래스로 만들어진 개별 객체. 같은 클래스에서 만들어도 각자 고유한 데이터를 가진다.

예: buddy = Dog("버디") → 버디라는 개 한 마리

파이썬 클래스 객체지향 프로그래밍 ▲ 객체지향 프로그래밍은 현실 세계의 사물을 코드로 표현하는 방식이다. 클래스는 설계도, 인스턴스는 그 설계도로 만들어진 실제 물건에 해당한다. (출처: Unsplash / 참고 이미지)

📝 클래스 정의 기본 — class 키워드와 __init__

class_basic.py — 클래스 정의와 인스턴스 생성
# 클래스 정의
class Dog:
    """개를 표현하는 클래스"""

    # __init__ : 생성자 — 인스턴스가 만들어질 때 자동 호출
    def __init__(self, name: str, breed: str, age: int):
        self.name  = name    # 인스턴스 변수 (각 인스턴스마다 독립)
        self.breed = breed
        self.age   = age

    # 인스턴스 메서드 — 첫 번째 인수는 항상 self
    def bark(self):
        print(f"{self.name}: 멍멍!")

    def info(self):
        print(f"{self.name} | {self.breed} | {self.age}살")

    def birthday(self):
        """나이를 1 늘린다"""
        self.age += 1
        print(f"{self.name} 생일 축하! 이제 {self.age}살")


# 인스턴스 생성 — 클래스명(인수) 형태
buddy = Dog("버디", "골든리트리버", 3)
navi  = Dog("나비", "말티즈", 5)

# 메서드 호출
buddy.bark()     # 버디: 멍멍!
navi.info()      # 나비 | 말티즈 | 5살
buddy.birthday() # 버디 생일 축하! 이제 4살

# 속성 직접 접근·수정
print(buddy.name)   # 버디
buddy.name = "버디2"  # 직접 수정도 가능
🔑
self란? 인스턴스 자기 자신을 가리키는 참조

self는 메서드가 호출될 때 파이썬이 자동으로 넘겨주는 인스턴스 자신의 참조입니다. buddy.bark()를 호출하면 파이썬이 내부적으로 Dog.bark(buddy)처럼 처리합니다.

  • 인스턴스 변수를 읽거나 쓸 때: self.name
  • 같은 클래스의 다른 메서드를 호출할 때: self.bark()
  • self는 관례적인 이름 — 파이썬 문법상 강제는 아니지만 반드시 지킬 것


📊 클래스 변수 vs 인스턴스 변수

class_vs_instance_var.py
class Dog:
    # 클래스 변수 — 모든 인스턴스가 공유
    species   = "Canis lupus familiaris"
    count     = 0   # 생성된 개 수 추적

    def __init__(self, name: str):
        self.name = name   # 인스턴스 변수 — 각자 독립
        Dog.count += 1      # 클래스 변수 수정은 클래스명으로

d1 = Dog("버디")
d2 = Dog("나비")
d3 = Dog("콩이")

print(Dog.count)      # 3  (클래스 변수 — 공유)
print(d1.count)       # 3  (인스턴스로도 접근 가능)
print(d1.name)        # 버디  (인스턴스 변수 — 독립)
print(d2.name)        # 나비
print(Dog.species)    # Canis lupus familiaris

# ⚠️ 인스턴스로 클래스 변수를 '수정'하면 인스턴스 변수가 새로 생성됨
d1.species = "개"     # d1만의 species가 생김 (클래스 변수 영향 없음)
print(d1.species)     # 개   (d1 인스턴스 변수)
print(Dog.species)    # Canis lupus familiaris (클래스 변수 그대로)

🔨 메서드 3종류 — 인스턴스·클래스·정적 메서드

method_types.py
class MathHelper:

    pi = 3.14159   # 클래스 변수

    # ① 인스턴스 메서드 — self 받음 / 인스턴스·클래스 변수 모두 접근
    def circle_area(self, r: float) -> float:
        return self.pi * r ** 2

    # ② 클래스 메서드 — cls 받음 / 클래스 변수 접근 / @classmethod 데코레이터
    @classmethod
    def set_pi(cls, value: float):
        cls.pi = value    # 클래스 변수 수정

    # ③ 정적 메서드 — self/cls 없음 / 독립적인 유틸리티 함수
    @staticmethod
    def add(a: float, b: float) -> float:
        return a + b

m = MathHelper()
print(m.circle_area(5))      # 78.53975 (인스턴스 메서드)
MathHelper.set_pi(3.14)      # 클래스 메서드 — 클래스명으로 호출
print(MathHelper.add(3, 4))  # 7  (정적 메서드)
메서드 선택 기준
  • 인스턴스 메서드: 인스턴스의 상태(self.변수)를 읽거나 수정해야 할 때 → 대부분의 경우
  • 클래스 메서드: 클래스 변수를 수정하거나, 대체 생성자(from_xxx 패턴)를 만들 때
  • 정적 메서드: 클래스·인스턴스와 관계없는 독립 유틸리티 함수 — 클래스 안에 묶어두는 것이 논리적으로 맞을 때

🌀 상속(Inheritance) — 부모 클래스 기능 물려받기

상속은 기존 클래스(부모·슈퍼클래스)의 속성과 메서드를 새 클래스(자식·서브클래스)가 그대로 물려받는 기능입니다. 공통 코드를 한 곳에서 관리하고, 자식 클래스에서 필요한 부분만 추가·수정(오버라이딩)할 수 있습니다.

inheritance.py
# 부모 클래스 (Parent / Super / Base class)
class Animal:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age  = age

    def introduce(self):
        print(f"이름: {self.name}, 나이: {self.age}살")

    def sound(self):
        print("...")   # 자식에서 오버라이드할 예정


# 자식 클래스 (Child / Sub / Derived class)
class Dog(Animal):   # Animal을 상속
    def __init__(self, name: str, age: int, breed: str):
        super().__init__(name, age)  # 부모 __init__ 호출
        self.breed = breed            # Dog만의 속성 추가

    def sound(self):             # 메서드 오버라이딩
        print(f"{self.name}: 멍멍!")

    def fetch(self):             # Dog만의 새 메서드
        print(f"{self.name}가 공을 가져옵니다!")


class Cat(Animal):
    def sound(self):
        print(f"{self.name}: 야옹~")


dog = Dog("버디", 3, "골든리트리버")
cat = Cat("나비", 5)

dog.introduce()   # 이름: 버디, 나이: 3살  (부모 메서드 그대로 사용)
dog.sound()       # 버디: 멍멍!            (오버라이딩된 메서드)
dog.fetch()       # 버디가 공을 가져옵니다!
cat.sound()       # 나비: 야옹~

# isinstance — 인스턴스 타입 확인
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True  (부모 타입으로도 True)
print(isinstance(dog, Cat))     # False
🔄
super()란? 부모 클래스의 메서드를 호출하는 방법

super()는 부모 클래스를 참조합니다. 자식 클래스의 __init__에서 super().__init__(...)을 호출하면 부모의 초기화 로직을 그대로 실행하면서 자식의 추가 속성만 따로 설정할 수 있습니다.

  • 부모 __init__ 호출을 빠뜨리면 부모 속성(name, age)이 초기화되지 않음
  • 오버라이드한 메서드 안에서 부모 버전도 실행하고 싶을 때 super().메서드명() 활용


🔒 캡슐화(Encapsulation) — 데이터 보호

캡슐화는 객체 내부 데이터를 외부에서 함부로 바꾸지 못하도록 보호하는 개념입니다. 파이썬은 완벽한 접근 제한 대신 이름 관례(naming convention)로 접근 수준을 표시합니다.

encapsulation.py
class BankAccount:
    def __init__(self, owner: str, balance: int):
        self.owner    = owner        # 공개 (public)
        self._balance = balance      # 보호 (protected) — 밑줄 1개: "내부용이니 직접 건드리지 마세요"
        self.__pin    = "1234"       # 비공개 (private) — 밑줄 2개: 이름 맹글링 적용

    # property — getter (읽기 전용 속성처럼 사용)
    @property
    def balance(self) -> int:
        return self._balance

    # setter — 유효성 검사 포함
    @balance.setter
    def balance(self, value: int):
        if value < 0:
            raise ValueError("잔액은 0원 이상이어야 합니다")
        self._balance = value

    def deposit(self, amount: int):
        self.balance += amount   # setter 호출
        print(f"입금 {amount}원 → 잔액 {self.balance}원")

    def withdraw(self, amount: int):
        if amount > self._balance:
            print("잔액 부족")
            return
        self.balance -= amount
        print(f"출금 {amount}원 → 잔액 {self.balance}원")


acc = BankAccount("Alice", 10000)
print(acc.balance)    # 10000  (property getter 호출)
acc.deposit(5000)    # 입금 5000원 → 잔액 15000원
acc.withdraw(3000)   # 출금 3000원 → 잔액 12000원

# property setter 유효성 검사
try:
    acc.balance = -100  # setter → ValueError 발생
except ValueError as e:
    print(e)

# __ (이름 맹글링) — 클래스 외부에서 직접 접근 시 이름이 바뀜
# acc.__pin         → AttributeError!
# acc._BankAccount__pin → 우회 가능하지만 하면 안 됨
파이썬 캡슐화 3단계 요약
  • name (밑줄 없음) → 공개(public): 누구나 접근 가능
  • _name (밑줄 1개) → 보호(protected): 관례상 "내부 또는 서브클래스에서만 사용" 표시. 기술적으론 막지 않음
  • __name (밑줄 2개) → 비공개(private): 이름 맹글링으로 _클래스명__name으로 변환 → 외부 접근 어렵게 만듦

매직 메서드(Magic Method) — 파이썬 내장 동작 정의하기

앞뒤로 언더스코어 두 개(__메서드명__)가 붙은 메서드를 매직 메서드(Dunder Method)라고 합니다. 파이썬이 특정 상황에서 자동으로 호출하며, 이를 정의하면 클래스가 내장 타입처럼 동작하게 됩니다.

메서드호출 시점예시
__init__(self, ...)인스턴스 생성 시Dog("버디")
__str__(self)str() 또는 print() 호출 시print(dog)
__repr__(self)repr() 또는 REPL 출력 시repr(dog)
__len__(self)len() 호출 시len(my_obj)
__eq__(self, other)== 비교 시a == b
__lt__(self, other)< 비교 시a < b
__add__(self, other)+ 연산 시a + b
__getitem__(self, key)obj[key] 접근 시obj[0]
__iter__(self)for문 또는 iter()for x in obj
__contains__(self, item)in 연산 시"x" in obj
__del__(self)인스턴스 소멸 시가비지 컬렉션
magic_methods.py
class Vector:
    """2D 벡터 클래스 — 매직 메서드 활용 예시"""

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __str__(self) -> str:
        return f"Vector({self.x}, {self.y})"

    def __repr__(self) -> str:
        return f"Vector(x={self.x}, y={self.y})"

    def __add__(self, other: 'Vector') -> 'Vector':
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __len__(self) -> int:
        return 2   # 2D 벡터는 항상 요소 2개

    def __abs__(self) -> float:
        return (self.x**2 + self.y**2) ** 0.5   # 벡터 크기


v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1)           # Vector(1, 2)       ← __str__
print(v1 + v2)     # Vector(4, 6)       ← __add__
print(v1 == Vector(1, 2))  # True         ← __eq__
print(len(v1))      # 2                  ← __len__
print(abs(v2))      # 5.0                ← __abs__

📝 10편 실습 문제

실습 1 — 도서 관리 클래스
  • Book 클래스: title·author·price·is_available 속성
  • 대출(borrow())·반납(return_book()) 메서드 구현
  • __str__으로 책 정보를 보기 좋게 출력
  • price는 property로 구현해 음수 입력 방어
실습 2 — 상속으로 직원 클래스 확장
  • Employee(name, salary) 부모 클래스 → introduce() 메서드
  • Manager(name, salary, team_size) 자식 클래스 → introduce() 오버라이딩 (부모 호출 후 팀 사이즈 추가)
practice_10.py — 예시 답안
# 실습 1 — Book 클래스
class Book:
    def __init__(self, title: str, author: str, price: int):
        self.title        = title
        self.author       = author
        self._price       = price
        self.is_available = True

    @property
    def price(self): return self._price

    @price.setter
    def price(self, v):
        if v < 0: raise ValueError("가격은 0원 이상")
        self._price = v

    def borrow(self):
        if not self.is_available:
            print(f"'{self.title}'은 이미 대출 중"); return
        self.is_available = False
        print(f"'{self.title}' 대출 완료")

    def return_book(self):
        self.is_available = True
        print(f"'{self.title}' 반납 완료")

    def __str__(self):
        status = "대출 가능" if self.is_available else "대출 중"
        return f"[{status}] {self.title} / {self.author} / {self.price}원"

b = Book("파이썬 완전 정복", "홍길동", 25000)
print(b)          # [대출 가능] 파이썬 완전 정복 / 홍길동 / 25000원
b.borrow()       # '파이썬 완전 정복' 대출 완료
b.borrow()       # '파이썬 완전 정복'은 이미 대출 중
b.return_book()  # '파이썬 완전 정복' 반납 완료

# 실습 2 — 상속
class Employee:
    def __init__(self, name: str, salary: int):
        self.name   = name
        self.salary = salary
    def introduce(self):
        print(f"이름: {self.name}, 연봉: {self.salary:,}원")

class Manager(Employee):
    def __init__(self, name, salary, team_size: int):
        super().__init__(name, salary)
        self.team_size = team_size
    def introduce(self):
        super().introduce()
        print(f"담당 팀원: {self.team_size}명")

Manager("Alice", 80000000, 10).introduce()
# 이름: Alice, 연봉: 80,000,000원
# 담당 팀원: 10명
10편 핵심 요약
  • 클래스 / 인스턴스: 클래스 = 설계도 / 인스턴스 = 실제 객체 / 클래스명(인수)로 생성
  • __init__: 생성자 — 인스턴스 생성 시 자동 호출 / 인스턴스 변수 초기화
  • self: 인스턴스 자기 자신 참조 / 모든 인스턴스 메서드 첫 번째 인수
  • 클래스 변수 vs 인스턴스 변수: 클래스 변수는 모든 인스턴스 공유 / 인스턴스 변수는 각자 독립
  • 메서드 3종: 인스턴스(self) / 클래스(@classmethod, cls) / 정적(@staticmethod, 인수 없음)
  • 상속: class 자식(부모): / super()로 부모 메서드 호출 / 오버라이딩으로 재정의
  • 캡슐화: 밑줄 없음(공개) / _밑줄(보호) / __밑줄(비공개·맹글링)
  • property: @property·@속성.setter로 getter/setter 구현 → 유효성 검사 포함
  • 매직 메서드: __str__·__repr__·__add__·__eq__·__len__ 등 — 내장 연산자·함수와 연동
  • isinstance(): 인스턴스 타입 확인 / 부모 클래스 타입으로도 True
다음 편 예고 11편 — 모듈과 패키지, pip 활용

import / from·import / 표준 라이브러리 / pip / 가상환경 / __name__

🐍

※ 본 포스팅은 Python 3 공식 문서(docs.python.org)의 클래스(Classes) 레퍼런스를 기반으로 작성된 학습용 콘텐츠입니다. 코드 예시는 Python 3.10 이상 환경에서 테스트되었습니다.