[2026] Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
이 글의 핵심
Python 클래스: 객체지향 프로그래밍(OOP) 클래스 기본·생성자와 소멸자.
들어가며
객체지향 프로그래밍(OOP)이란?
객체지향 프로그래밍(Object-Oriented Programming)은 프로그램을 객체(Object)들의 모음으로 보는 프로그래밍 패러다임입니다. 절차지향 vs 객체지향: 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 절차지향: 함수 중심
def create_account(owner, balance):
return {"owner": owner, "balance": balance}
def deposit(account, amount):
account[balance] += amount
def withdraw(account, amount):
if amount > account[balance]:
return False
account[balance] -= amount
return True
account = create_account("철수", 10000)
deposit(account, 5000)
print(account[balance]) # 15000
# 객체지향: 객체 중심
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
return False
self.balance -= amount
return True
account = BankAccount("철수", 10000)
account.deposit(5000)
print(account.balance) # 15000
OOP의 장점:
- ✅ 캡슐화: 데이터와 기능을 하나로 묶음
- ✅ 재사용성: 상속으로 코드 재사용
- ✅ 유지보수: 변경 영향 범위 최소화
- ✅ 확장성: 새 기능 추가 용이
실무 활용 사례: 데이터 분석, 웹 개발, 자동화 프로젝트에서 실제로 사용한 패턴과 코드를 바탕으로 정리했습니다. 초보자가 흔히 겪는 오류와 해결법을 포함합니다.
실무에서 느낀 Python의 매력
처음 Python을 배울 때는 “이게 정말 프로그래밍 언어인가?” 싶을 정도로 간결했습니다. C++에서 10줄로 작성하던 코드가 Python에서는 2~3줄로 끝나는 경우가 많았죠. 특히 데이터 분석 프로젝트를 진행하면서 Pandas와 NumPy의 강력함을 체감했습니다. 엑셀로 몇 시간 걸리던 작업이 Python 스크립트로는 몇 초 만에 끝나는 걸 보고 동료들이 놀라워했던 기억이 납니다. 하지만 처음부터 순탄하지만은 않았습니다. 들여쓰기 하나 잘못해서 몇 시간을 헤맨 적도 있고, 가상환경 설정이 꼬여서 프로젝트 전체를 다시 시작한 적도 있습니다. 이런 시행착오를 겪으며 깨달은 건, 환경 설정을 처음부터 제대로 하는 것이 얼마나 중요한지였습니다. 이 글에서는 제가 겪은 실수들을 바탕으로, 여러분이 같은 시행착오를 겪지 않도록 실전 팁을 담았습니다.
1. 클래스 기본
클래스와 객체
클래스(Class)는 객체를 만들기 위한 설계도(blueprint)입니다. 붕어빵 틀에 반죽을 붓으면 여러 개의 붕어빵이 나오듯, 같은 클래스에서 찍어낸 인스턴스들은 구조는 같고 내용(속성 값)은 다를 수 있습니다.
객체(Object) 또는 인스턴스(Instance)는 클래스로부터 만들어진 실체입니다.
아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 클래스 정의 (설계도)
class Person:
pass # 아무것도 없는 클래스
# 객체 생성 (인스턴스화)
person1 = Person() # Person 클래스의 인스턴스
person2 = Person() # 또 다른 인스턴스
print(type(person1)) # <class '__main__.Person'>
print(person1 == person2) # False (서로 다른 객체)
클래스 정의와 메서드
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Person:
# 생성자: 객체 생성 시 자동 호출
def __init__(self, name, age):
self.name = name # 인스턴스 변수
self.age = age
# 인스턴스 메서드
def greet(self):
return f"안녕하세요, {self.name}입니다."
def get_age(self):
return self.age
def is_adult(self):
return self.age >= 18
# 객체 생성
person1 = Person("철수", 25)
person2 = Person("영희", 30)
# 메서드 호출
print(person1.greet()) # 안녕하세요, 철수입니다.
print(person2.age) # 30
print(person1.is_adult()) # True
# 속성 접근
print(person1.name) # 철수
# 속성 변경
person1.age = 26
print(person1.age) # 26
self의 이해
self는 인스턴스 자신을 가리키는 참조입니다: 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Counter:
def __init__(self):
# self: 현재 생성되는 인스턴스를 가리킴
# self.count: 이 인스턴스의 count 속성
self.count = 0
# 각 인스턴스는 독립적인 count를 가짐
def increment(self):
# self.count: 이 메서드를 호출한 인스턴스의 count
self.count += 1
return self.count
# 각 인스턴스는 독립적인 메모리 공간을 가짐
counter1 = Counter() # counter1의 count = 0
counter2 = Counter() # counter2의 count = 0 (별도 메모리)
counter1.increment() # counter1.count = 1
counter1.increment() # counter1.count = 2
print(counter1.count) # 2
counter2.increment() # counter2.count = 1
print(counter2.count) # 1 (counter1과 독립적)
# self는 자동으로 전달됨
# counter1.increment()는 내부적으로 Counter.increment(counter1)로 호출됨
# Python이 첫 번째 인자로 인스턴스를 자동 전달
self의 동작 원리: 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Example:
def __init__(self, value):
self.value = value
def show(self):
print(f"값: {self.value}")
# 인스턴스 메서드 호출
obj = Example(10)
obj.show() # 값: 10
# 위 코드는 실제로 아래와 같이 동작:
Example.show(obj) # 값: 10
# Python이 obj를 첫 번째 인자(self)로 자동 전달
# 따라서 메서드 정의 시 첫 번째 매개변수는 항상 self
self를 사용하지 않으면: 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class BadCounter:
def __init__(self):
count = 0 # ❌ self 없음 → 지역 변수
def increment(self):
# count += 1 # ❌ NameError: count가 정의되지 않음
pass
# self를 붙여야 인스턴스 변수가 됨
class GoodCounter:
def __init__(self):
self.count = 0 # ✅ 인스턴스 변수
def increment(self):
self.count += 1 # ✅ 인스턴스 변수 접근
self vs 클래스 변수: 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Student:
# 클래스 변수: 모든 인스턴스가 공유
school = "서울고등학교"
def __init__(self, name):
# 인스턴스 변수: 각 인스턴스마다 독립적
self.name = name
s1 = Student("철수")
s2 = Student("영희")
print(s1.name) # 철수 (인스턴스 변수, 독립적)
print(s2.name) # 영희 (인스턴스 변수, 독립적)
print(s1.school) # 서울고등학교 (클래스 변수, 공유)
print(s2.school) # 서울고등학교 (클래스 변수, 공유)
# 클래스 변수 변경 (모든 인스턴스에 영향)
Student.school = "부산고등학교"
print(s1.school) # 부산고등학교
print(s2.school) # 부산고등학교
2. 생성자와 소멸자
init (생성자)
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
print(f"{owner}님의 계좌 생성 (잔액: {balance}원)")
def deposit(self, amount):
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return "잔액 부족"
self.balance -= amount
return self.balance
# 사용
account = BankAccount("철수", 10000)
# 철수님의 계좌 생성 (잔액: 10000원)
account.deposit(5000)
print(account.balance) # 15000
del (소멸자)
아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class FileHandler:
def __init__(self, filename):
self.file = open(filename, 'w')
print(f"{filename} 열림")
def __del__(self):
self.file.close()
print("파일 닫힘")
# 사용
handler = FileHandler("test.txt")
# test.txt 열림
del handler
# 파일 닫힘
3. 클래스 변수 vs 인스턴스 변수
차이점 이해
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Dog:
# 클래스 변수: 모든 인스턴스가 공유
species = "Canis familiaris"
count = 0 # 생성된 개 수
def __init__(self, name, age):
# 인스턴스 변수: 각 인스턴스마다 독립적
self.name = name
self.age = age
Dog.count += 1 # 클래스 변수 증가
dog1 = Dog("바둑이", 3)
dog2 = Dog("멍멍이", 5)
# 클래스 변수 접근
print(dog1.species) # Canis familiaris
print(dog2.species) # Canis familiaris
print(Dog.count) # 2 (생성된 개 수)
# 인스턴스 변수 접근
print(dog1.name) # 바둑이
print(dog2.name) # 멍멍이
# 클래스 변수 변경 (모든 인스턴스 영향)
Dog.species = "개"
print(dog1.species) # 개
print(dog2.species) # 개
# 인스턴스 변수 변경 (해당 인스턴스만 영향)
dog1.name = "뽀삐"
print(dog1.name) # 뽀삐
print(dog2.name) # 멍멍이 (변경 안 됨)
클래스 변수 주의사항
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MyClass:
shared_list = [] # 클래스 변수 (위험!)
def __init__(self, value):
self.shared_list.append(value) # 모든 인스턴스가 공유
obj1 = MyClass(1)
obj2 = MyClass(2)
print(obj1.shared_list) # [1, 2] (의도하지 않은 공유!)
print(obj2.shared_list) # [1, 2]
# 올바른 방법: 인스턴스 변수 사용
class MyClass:
def __init__(self, value):
self.my_list = [] # 인스턴스 변수
self.my_list.append(value)
obj1 = MyClass(1)
obj2 = MyClass(2)
print(obj1.my_list) # [1]
print(obj2.my_list) # [2]
클래스 변수 활용 예제
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Employee:
company = "ABC Corp" # 회사명 (모든 직원 공유)
employee_count = 0 # 직원 수
def __init__(self, name, position):
self.name = name
self.position = position
Employee.employee_count += 1
@classmethod
def get_employee_count(cls):
return cls.employee_count
def __del__(self):
Employee.employee_count -= 1
# 사용
emp1 = Employee("홍길동", "개발자")
emp2 = Employee("김철수", "디자이너")
print(f"회사: {Employee.company}") # ABC Corp
print(f"직원 수: {Employee.get_employee_count()}") # 2
del emp1
print(f"직원 수: {Employee.get_employee_count()}") # 1
4. 상속 (Inheritance)
상속이란?
상속(Inheritance)은 기존 클래스의 속성과 메서드를 재사용하는 메커니즘입니다. 용어:
- 부모 클래스(Parent Class) = 기반 클래스(Base Class) = 슈퍼 클래스(Super Class)
- 자식 클래스(Child Class) = 파생 클래스(Derived Class) = 서브 클래스(Sub Class)
기본 상속
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 부모 클래스
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "소리를 냅니다"
def move(self):
return f"{self.name}이(가) 움직입니다"
# 자식 클래스 1
class Dog(Animal): # Animal 상속
def speak(self): # 메서드 오버라이딩
return f"{self.name}: 멍멍!"
# 자식 클래스 2
class Cat(Animal):
def speak(self):
return f"{self.name}: 야옹~"
# 사용
dog = Dog("바둑이")
cat = Cat("나비")
print(dog.speak()) # 바둑이: 멍멍! (오버라이딩된 메서드)
print(dog.move()) # 바둑이이(가) 움직입니다 (상속받은 메서드)
print(cat.speak()) # 나비: 야옹~
# isinstance(): 타입 확인
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True (부모 클래스도 True)
print(isinstance(dog, Cat)) # False
# issubclass(): 상속 관계 확인
print(issubclass(Dog, Animal)) # True
print(issubclass(Animal, Dog)) # False
super() 사용
super()는 부모 클래스의 메서드를 호출할 때 사용합니다. 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def get_info(self):
return f"{self.name} - {self.salary:,}원"
def work(self):
return f"{self.name}이(가) 일합니다"
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary) # 부모 생성자 호출
self.team_size = team_size
def get_info(self):
base_info = super().get_info() # 부모 메서드 호출
return f"{base_info} (팀원: {self.team_size}명)"
def manage_team(self):
return f"{self.name}이(가) {self.team_size}명의 팀을 관리합니다"
# 사용
manager = Manager("김팀장", 5000000, 5)
print(manager.get_info()) # 김팀장 - 5,000,000원 (팀원: 5명)
print(manager.work()) # 김팀장이(가) 일합니다 (상속)
print(manager.manage_team()) # 김팀장이(가) 5명의 팀을 관리합니다
다중 상속
Python은 다중 상속을 지원합니다 (C++과 유사, Java는 불가). 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Flyer:
def fly(self):
return "날아갑니다"
class Swimmer:
def swim(self):
return "헤엄칩니다"
# 다중 상속
class Duck(Flyer, Swimmer):
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name}: 꽥꽥!"
duck = Duck("도널드")
print(duck.fly()) # 날아갑니다
print(duck.swim()) # 헤엄칩니다
print(duck.speak()) # 도널드: 꽥꽥!
# MRO (Method Resolution Order): 메서드 검색 순서
print(Duck.__mro__)
# (<class '__main__.Duck'>, <class '__main__.Flyer'>,
# <class '__main__.Swimmer'>, <class 'object'>)
상속 실전 예제
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 게임 캐릭터 시스템
class Character:
def __init__(self, name, hp, attack):
self.name = name
self.hp = hp
self.attack = attack
def take_damage(self, damage):
self.hp -= damage
if self.hp < 0:
self.hp = 0
return self.hp
def is_alive(self):
return self.hp > 0
def basic_attack(self, target):
return target.take_damage(self.attack)
class Warrior(Character):
def __init__(self, name, hp, attack, defense):
super().__init__(name, hp, attack)
self.defense = defense
def take_damage(self, damage):
reduced = max(0, damage - self.defense)
return super().take_damage(reduced)
def shield_bash(self, target):
return target.take_damage(self.attack * 1.5)
class Mage(Character):
def __init__(self, name, hp, attack, mana):
super().__init__(name, hp, attack)
self.mana = mana
def fireball(self, target):
if self.mana >= 20:
self.mana -= 20
return target.take_damage(self.attack * 2)
return 0
# 전투 시뮬레이션
warrior = Warrior("전사", 150, 20, 5)
mage = Mage("마법사", 100, 30, 50)
print(f"{warrior.name} HP: {warrior.hp}") # 전사 HP: 150
print(f"{mage.name} HP: {mage.hp}") # 마법사 HP: 100
# 전사가 마법사 공격
mage.basic_attack(warrior)
print(f"{warrior.name} HP: {warrior.hp}") # 전사 HP: 125 (30-5=25 데미지)
# 마법사가 파이어볼
mage.fireball(warrior)
print(f"{warrior.name} HP: {warrior.hp}") # 전사 HP: 70 (60-5=55 데미지)
print(f"{mage.name} 마나: {mage.mana}") # 마법사 마나: 30
5. 캡슐화 (Encapsulation)
캡슐화란?
캡슐화(Encapsulation)는 데이터를 외부로부터 보호하고, 접근을 제어하는 것입니다. Python의 접근 제어:
public: 제한 없음 (기본)_protected: 관례상 내부 사용 (강제 아님)__private: Name Mangling으로 외부 접근 차단
Private 변수 (__prefix)
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # public
self._account_number = "123" # protected (관례)
self.__balance = balance # private (__)
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
account = BankAccount("철수", 10000)
# public 접근
print(account.owner) # 철수
# protected 접근 (가능하지만 권장 안 함)
print(account._account_number) # 123
# private 접근 시도
# print(account.__balance) # AttributeError: 'BankAccount' object has no attribute '__balance'
# 메서드로 접근 (올바른 방법)
print(account.get_balance()) # 10000
# Name Mangling: 실제로는 _ClassName__attribute로 변환됨
print(account._BankAccount__balance) # 10000 (가능하지만 절대 하지 말 것!)
@property 데코레이터
@property는 메서드를 속성처럼 사용할 수 있게 합니다. Getter/Setter를 우아하게 구현합니다. 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Circle:
def __init__(self, radius):
self._radius = radius # protected (관례)
@property
def radius(self):
"""Getter: circle.radius로 접근"""
return self._radius
@radius.setter
def radius(self, value):
"""Setter: circle.radius = value로 설정"""
if value < 0:
raise ValueError("반지름은 양수여야 합니다")
self._radius = value
@property
def area(self):
"""읽기 전용 속성 (setter 없음)"""
return 3.14159 * self._radius ** 2
@property
def diameter(self):
return self._radius * 2
@diameter.setter
def diameter(self, value):
self._radius = value / 2
# 사용
circle = Circle(5)
print(circle.radius) # 5 (메서드지만 속성처럼 접근)
print(circle.area) # 78.53975
circle.radius = 10 # setter 호출
print(circle.area) # 314.159
# circle.area = 100 # AttributeError: can't set attribute (읽기 전용)
circle.diameter = 20 # diameter setter
print(circle.radius) # 10.0
# 유효성 검사
# circle.radius = -5 # ValueError: 반지름은 양수여야 합니다
캡슐화 실전 예제
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("절대영도 이하는 불가능합니다")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
@property
def kelvin(self):
return self._celsius + 273.15
# 사용
temp = Temperature(25)
print(f"섭씨: {temp.celsius}°C") # 섭씨: 25°C
print(f"화씨: {temp.fahrenheit}°F") # 화씨: 77.0°F
print(f"켈빈: {temp.kelvin}K") # 켈빈: 298.15K
temp.fahrenheit = 100 # 화씨로 설정
print(f"섭씨: {temp.celsius}°C") # 섭씨: 37.77777777777778°C
# temp.celsius = -300 # ValueError: 절대영도 이하는 불가능합니다
6. 특수 메서드 (Magic Methods / Dunder Methods)
특수 메서드란?
특수 메서드(Magic Methods)는 __method__ 형태로, Python의 내장 연산자와 함수를 커스터마이즈합니다.
주요 특수 메서드:
| 메서드 | 설명 | 호출 방법 |
|---|---|---|
__init__ | 생성자 | obj = MyClass() |
__str__ | 문자열 표현 (사용자용) | str(obj), print(obj) |
__repr__ | 문자열 표현 (개발자용) | repr(obj) |
__len__ | 길이 | len(obj) |
__add__ | 덧셈 | obj1 + obj2 |
__eq__ | 같음 비교 | obj1 == obj2 |
__lt__ | 작음 비교 | obj1 < obj2 |
__getitem__ | 인덱싱 | obj[key] |
__setitem__ | 인덱스 할당 | obj[key] = value |
__call__ | 호출 | obj() |
특수 메서드 구현
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
"""print()에서 사용"""
return f"Vector({self.x}, {self.y})"
def __repr__(self):
"""개발자용 표현 (디버깅)"""
return f"Vector(x={self.x}, y={self.y})"
def __add__(self, other):
"""+ 연산자 오버로딩"""
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""- 연산자"""
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""* 연산자 (스칼라 곱)"""
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other):
"""== 연산자"""
return self.x == other.x and self.y == other.y
def __len__(self):
"""len() 함수 (벡터 크기)"""
return int((self.x ** 2 + self.y ** 2) ** 0.5)
def __getitem__(self, index):
"""인덱싱: v[0], v[1]"""
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("Vector index out of range")
# 사용
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1) # Vector(1, 2) (__str__)
print(repr(v1)) # Vector(x=1, y=2) (__repr__)
v3 = v1 + v2 # __add__
print(v3) # Vector(4, 6)
v4 = v2 - v1 # __sub__
print(v4) # Vector(2, 2)
v5 = v1 * 3 # __mul__
print(v5) # Vector(3, 6)
print(v1 == v2) # False (__eq__)
print(len(v1)) # 2 (__len__)
print(v1[0], v1[1]) # 1 2 (__getitem__)
컨테이너 특수 메서드
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Inventory:
def __init__(self):
self.items = {}
def __setitem__(self, key, value):
"""inventory[key] = value"""
self.items[key] = value
def __getitem__(self, key):
"""inventory[key]"""
return self.items.get(key, 0)
def __delitem__(self, key):
"""del inventory[key]"""
if key in self.items:
del self.items[key]
def __contains__(self, key):
"""key in inventory"""
return key in self.items
def __len__(self):
"""len(inventory)"""
return len(self.items)
def __str__(self):
return str(self.items)
# 사용
inv = Inventory()
inv[sword] = 1 # __setitem__
inv[potion] = 5
print(inv[sword]) # 1 (__getitem__)
print("sword" in inv) # True (__contains__)
print(len(inv)) # 2 (__len__)
del inv[sword] # __delitem__
print(inv) # {'potion': 5} (__str__)
call 메서드
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
"""객체를 함수처럼 호출"""
return x * self.factor
# 사용
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10 (객체를 함수처럼 호출)
print(triple(5)) # 15
# 실전 예제: 카운터
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
counter = Counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
7. 클래스 메서드와 정적 메서드
메서드 종류 비교
| 메서드 | 첫 인자 | 접근 가능 | 사용 시점 |
|---|---|---|---|
| 인스턴스 메서드 | self | 인스턴스 변수 | 인스턴스 데이터 필요 |
| 클래스 메서드 | cls | 클래스 변수 | 대체 생성자, 클래스 데이터 |
| 정적 메서드 | 없음 | 제한 없음 | 유틸리티 함수 |
클래스 메서드 (@classmethod)
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
"""대체 생성자: 출생년도로 객체 생성"""
age = date.today().year - birth_year
return cls(name, age) # cls는 Person 클래스
@classmethod
def get_species(cls):
"""클래스 정보 반환"""
return "Homo sapiens"
def greet(self):
return f"안녕하세요, {self.name}입니다. {self.age}살입니다."
# 일반 생성자
person1 = Person("철수", 25)
# 클래스 메서드로 생성
person2 = Person.from_birth_year("영희", 1995)
print(person1.greet()) # 안녕하세요, 철수입니다. 25살입니다.
print(person2.greet()) # 안녕하세요, 영희입니다. 31살입니다.
print(Person.get_species()) # Homo sapiens
정적 메서드 (@staticmethod)
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MathUtils:
PI = 3.14159
@staticmethod
def is_even(n):
"""정적 메서드: self나 cls 불필요"""
return n % 2 == 0
@staticmethod
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
@staticmethod
def factorial(n):
if n <= 1:
return 1
return n * MathUtils.factorial(n - 1)
def __init__(self, radius):
self.radius = radius
def area(self):
"""인스턴스 메서드: self 필요"""
return MathUtils.PI * self.radius ** 2
# 정적 메서드 사용 (인스턴스 없이 호출 가능)
print(MathUtils.is_even(4)) # True
print(MathUtils.is_prime(7)) # True
print(MathUtils.factorial(5)) # 120
# 인스턴스 메서드 사용
circle = MathUtils(5)
print(circle.area()) # 78.53975
# 인스턴스에서도 정적 메서드 호출 가능 (권장 안 함)
print(circle.is_even(4)) # True
실전 예제: 날짜 유틸리티
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
from datetime import datetime
class DateUtils:
@staticmethod
def is_weekend(date_str):
"""날짜가 주말인지 확인"""
date = datetime.strptime(date_str, "%Y-%m-%d")
return date.weekday() >= 5 # 5: 토요일, 6: 일요일
@staticmethod
def days_between(date1_str, date2_str):
"""두 날짜 사이의 일수"""
date1 = datetime.strptime(date1_str, "%Y-%m-%d")
date2 = datetime.strptime(date2_str, "%Y-%m-%d")
return abs((date2 - date1).days)
@classmethod
def today(cls):
"""오늘 날짜 반환"""
return datetime.now().strftime("%Y-%m-%d")
# 사용
print(DateUtils.is_weekend("2026-03-29")) # True (일요일)
print(DateUtils.days_between("2026-03-01", "2026-03-29")) # 28
print(DateUtils.today()) # 2026-03-29
8. 추상 클래스 (Abstract Class)
추상 클래스란?
추상 클래스(Abstract Class)는 직접 인스턴스화할 수 없고, 자식 클래스가 반드시 구현해야 하는 메서드를 정의합니다. 다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
from abc import ABC, abstractmethod
class Shape(ABC): # ABC 상속
@abstractmethod
def area(self):
"""자식 클래스에서 반드시 구현해야 함"""
pass
@abstractmethod
def perimeter(self):
pass
def describe(self):
"""일반 메서드 (구현 가능)"""
return f"넓이: {self.area()}, 둘레: {self.perimeter()}"
# shape = Shape() # TypeError: Can't instantiate abstract class
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
# 사용
rect = Rectangle(5, 10)
print(rect.describe()) # 넓이: 50, 둘레: 30
circle = Circle(5)
print(circle.describe()) # 넓이: 78.53975, 둘레: 31.4159
9. 다형성 (Polymorphism)
다형성이란?
다형성(Polymorphism)은 같은 인터페이스로 다른 동작을 수행하는 것입니다. 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "멍멍!"
class Cat(Animal):
def speak(self):
return "야옹~"
class Cow(Animal):
def speak(self):
return "음메~"
# 다형성: 같은 메서드 이름, 다른 동작
def make_sound(animal):
print(animal.speak())
animals = [Dog(), Cat(), Cow()]
for animal in animals:
make_sound(animal)
# 멍멍!
# 야옹~
# 음메~
다형성 실전 예제: 결제 시스템
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.card_number = card_number
def pay(self, amount):
return f"신용카드({self.card_number[-4:]})로 {amount:,}원 결제"
class BankTransfer(PaymentMethod):
def __init__(self, account_number):
self.account_number = account_number
def pay(self, amount):
return f"계좌이체({self.account_number})로 {amount:,}원 결제"
class KakaoPay(PaymentMethod):
def __init__(self, phone):
self.phone = phone
def pay(self, amount):
return f"카카오페이({self.phone})로 {amount:,}원 결제"
# 결제 처리 함수 (다형성)
def process_payment(payment_method, amount):
print(payment_method.pay(amount))
# 사용
methods = [
CreditCard("1234-5678-9012-3456"),
BankTransfer("110-123-456789"),
KakaoPay("010-1234-5678")
]
for method in methods:
process_payment(method, 50000)
# 출력:
# 신용카드(3456)로 50,000원 결제
# 계좌이체(110-123-456789)로 50,000원 결제
# 카카오페이(010-1234-5678)로 50,000원 결제
10. 실전 프로젝트: 도서 관리 시스템
다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
from datetime import datetime, timedelta
class Book:
def __init__(self, isbn, title, author, available=True):
self.isbn = isbn
self.title = title
self.author = author
self.available = available
def __str__(self):
status = "대출 가능" if self.available else "대출 중"
return f"[{self.isbn}] {self.title} - {self.author} ({status})"
def __eq__(self, other):
return self.isbn == other.isbn
class Member:
def __init__(self, member_id, name):
self.member_id = member_id
self.name = name
self.borrowed_books = []
def __str__(self):
return f"회원 {self.name}({self.member_id})"
class Library:
def __init__(self):
self.books = {}
self.members = {}
self.loan_records = []
def add_book(self, book):
self.books[book.isbn] = book
print(f"도서 추가: {book.title}")
def register_member(self, member):
self.members[member.member_id] = member
print(f"회원 등록: {member.name}")
def borrow_book(self, member_id, isbn):
if member_id not in self.members:
return "등록되지 않은 회원입니다"
if isbn not in self.books:
return "존재하지 않는 도서입니다"
book = self.books[isbn]
member = self.members[member_id]
if not book.available:
return f"{book.title}은(는) 이미 대출 중입니다"
book.available = False
member.borrowed_books.append(isbn)
due_date = datetime.now() + timedelta(days=14)
self.loan_records.append({
"member": member_id,
"book": isbn,
"borrow_date": datetime.now(),
"due_date": due_date
})
return f"{member.name}님이 '{book.title}'을(를) 대출했습니다 (반납일: {due_date.strftime('%Y-%m-%d')})"
def return_book(self, member_id, isbn):
if isbn not in self.books:
return "존재하지 않는 도서입니다"
book = self.books[isbn]
member = self.members[member_id]
if isbn not in member.borrowed_books:
return "대출하지 않은 도서입니다"
book.available = True
member.borrowed_books.remove(isbn)
return f"{member.name}님이 '{book.title}'을(를) 반납했습니다"
def list_available_books(self):
available = [book for book in self.books.values() if book.available]
return available
# 사용 예제
library = Library()
# 도서 추가
library.add_book(Book("978-1", "파이썬 입문", "홍길동"))
library.add_book(Book("978-2", "알고리즘 정복", "김철수"))
library.add_book(Book("978-3", "데이터 과학", "이영희"))
# 회원 등록
library.register_member(Member("M001", "박민수"))
library.register_member(Member("M002", "정수진"))
# 대출
print(library.borrow_book("M001", "978-1"))
# 박민수님이 '파이썬 입문'을(를) 대출했습니다 (반납일: 2026-04-12)
print(library.borrow_book("M002", "978-1"))
# 파이썬 입문은(는) 이미 대출 중입니다
# 반납
print(library.return_book("M001", "978-1"))
# 박민수님이 '파이썬 입문'을(를) 반납했습니다
# 대출 가능 도서 목록
print("\n대출 가능 도서:")
for book in library.list_available_books():
print(f" - {book}")
11. OOP 4대 원칙
1. 캡슐화 (Encapsulation)
데이터와 메서드를 하나로 묶고, 외부 접근을 제어합니다. 아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Account:
def __init__(self, balance):
self.__balance = balance # private
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
# 직접 접근 불가, 메서드로만 접근
2. 상속 (Inheritance)
기존 클래스를 확장하여 재사용합니다. 아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class Vehicle:
def move(self):
pass
class Car(Vehicle):
def move(self):
return "도로를 달립니다"
3. 다형성 (Polymorphism)
같은 인터페이스로 다른 동작을 수행합니다. 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
def process(shape):
print(shape.area()) # 각 도형마다 다른 계산
process(Rectangle(5, 10)) # 50
process(Circle(5)) # 78.54
4. 추상화 (Abstraction)
복잡한 내부 구현을 숨기고 필요한 기능만 노출합니다. 아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Database(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def query(self, sql):
pass
# 사용자는 connect()와 query()만 알면 됨
12. 자주 하는 실수와 해결법
실수 1: __init__에서 self 누락
아래 코드는 python를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 잘못된 방법
class Person:
def __init__(name, age): # self 누락!
self.name = name
self.age = age
# person = Person("철수", 25) # TypeError
# ✅ 올바른 방법
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
실수 2: 클래스 변수를 인스턴스 변수로 착각
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 잘못된 방법
class MyClass:
items = [] # 클래스 변수 (모든 인스턴스 공유!)
def add(self, item):
self.items.append(item)
obj1 = MyClass()
obj2 = MyClass()
obj1.add(1)
obj2.add(2)
print(obj1.items) # [1, 2] (의도하지 않은 공유)
# ✅ 올바른 방법
class MyClass:
def __init__(self):
self.items = [] # 인스턴스 변수
def add(self, item):
self.items.append(item)
실수 3: super() 호출 누락
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 잘못된 방법
class Parent:
def __init__(self, name):
self.name = name
class Child(Parent):
def __init__(self, name, age):
# super().__init__(name) 누락!
self.age = age
# child = Child("철수", 25)
# print(child.name) # AttributeError
# ✅ 올바른 방법
class Child(Parent):
def __init__(self, name, age):
super().__init__(name)
self.age = age
실수 4: @property setter 없이 할당
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 잘못된 방법
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
# setter 없음!
circle = Circle(5)
# circle.radius = 10 # AttributeError: can't set attribute
# ✅ 올바른 방법
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
self._radius = value
13. 연습 문제
문제 1: 은행 계좌 클래스
입금, 출금, 잔액 조회 기능을 가진 BankAccount 클래스를 구현하세요.
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance
self.transaction_history = []
def deposit(self, amount):
if amount <= 0:
return "입금액은 양수여야 합니다"
self.__balance += amount
self.transaction_history.append(f"입금: +{amount:,}원")
return f"입금 완료. 잔액: {self.__balance:,}원"
def withdraw(self, amount):
if amount <= 0:
return "출금액은 양수여야 합니다"
if amount > self.__balance:
return "잔액이 부족합니다"
self.__balance -= amount
self.transaction_history.append(f"출금: -{amount:,}원")
return f"출금 완료. 잔액: {self.__balance:,}원"
@property
def balance(self):
return self.__balance
def get_history(self):
return "\n".join(self.transaction_history)
# 테스트
account = BankAccount("홍길동", 10000)
print(account.deposit(5000)) # 입금 완료. 잔액: 15,000원
print(account.withdraw(3000)) # 출금 완료. 잔액: 12,000원
print(f"현재 잔액: {account.balance:,}원") # 현재 잔액: 12,000원
print("\n거래 내역:")
print(account.get_history())
문제 2: 직원 관리 시스템
Employee 부모 클래스와 Developer, Designer 자식 클래스를 구현하세요.
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Employee:
company = "ABC Corp"
def __init__(self, name, salary):
self.name = name
self.salary = salary
def get_annual_salary(self):
return self.salary * 12
def __str__(self):
return f"{self.name} - {self.salary:,}원"
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def code(self):
return f"{self.name}이(가) {self.language}로 코딩합니다"
def __str__(self):
return f"{super().__str__()} [개발자 - {self.language}]"
class Designer(Employee):
def __init__(self, name, salary, tool):
super().__init__(name, salary)
self.tool = tool
def design(self):
return f"{self.name}이(가) {self.tool}로 디자인합니다"
def __str__(self):
return f"{super().__str__()} [디자이너 - {self.tool}]"
# 테스트
dev = Developer("홍길동", 5000000, "Python")
designer = Designer("김철수", 4500000, "Figma")
print(dev) # 홍길동 - 5,000,000원 [개발자 - Python]
print(designer) # 김철수 - 4,500,000원 [디자이너 - Figma]
print(dev.code()) # 홍길동이(가) Python로 코딩합니다
print(designer.design()) # 김철수이(가) Figma로 디자인합니다
print(f"연봉: {dev.get_annual_salary():,}원") # 연봉: 60,000,000원
문제 3: 쇼핑몰 상품 클래스
특수 메서드를 활용한 Product 클래스를 구현하세요.
다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Product:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def __str__(self):
return f"{self.name} - {self.price:,}원 (재고: {self.quantity}개)"
def __add__(self, other):
"""두 상품의 총 가격"""
return self.price * self.quantity + other.price * other.quantity
def __mul__(self, n):
"""수량 증가"""
return Product(self.name, self.price, self.quantity * n)
def __lt__(self, other):
"""가격 비교 (정렬용)"""
return self.price < other.price
def __len__(self):
"""재고 수량"""
return self.quantity
# 테스트
p1 = Product("노트북", 1200000, 2)
p2 = Product("마우스", 30000, 5)
print(p1) # 노트북 - 1,200,000원 (재고: 2개)
print(p2) # 마우스 - 30,000원 (재고: 5개)
total = p1 + p2
print(f"총 가격: {total:,}원") # 총 가격: 2,550,000원
p3 = p1 * 2 # 수량 2배
print(p3) # 노트북 - 1,200,000원 (재고: 4개)
products = [p1, p2]
products.sort() # __lt__로 정렬
print("가격순 정렬:", [p.name for p in products]) # ['마우스', '노트북']
print(f"재고: {len(p1)}개") # 재고: 2개
정리
핵심 요약
- 클래스: 데이터 + 기능을 묶는 설계도
- 객체: 클래스로부터 생성된 실체
- init: 생성자, 객체 초기화
- self: 인스턴스 자신을 가리키는 참조
- 상속: 코드 재사용,
super()로 부모 호출 - 캡슐화:
__private,@property로 데이터 보호 - 다형성: 같은 인터페이스, 다른 동작
- 추상 클래스:
ABC,@abstractmethod로 인터페이스 정의 - 특수 메서드:
__str__,__add__등으로 연산자 오버로딩 - 클래스/정적 메서드:
@classmethod,@staticmethod
OOP 설계 원칙
- 단일 책임 원칙: 클래스는 하나의 책임만
- 개방-폐쇄 원칙: 확장에는 열려있고, 수정에는 닫혀있어야
- 리스코프 치환 원칙: 자식 클래스는 부모 클래스를 대체 가능해야
- 인터페이스 분리 원칙: 클라이언트는 사용하지 않는 메서드에 의존하지 않아야
- 의존성 역전 원칙: 구체화가 아닌 추상화에 의존해야
다음 단계
- Python 모듈과 패키지
- Python 예외 처리
- Python 데코레이터