“파이썬 클래스가 어려워요. 언제, 왜 사용해야 하나요?”
파이썬은 대표적인 객체지향 프로그래밍(OOP) 언어입니다. 클래스는 연관된 데이터와 기능을 하나로 묶어 코드를 구조화하고 재사용성을 높이는 강력한 도구입니다. 이 글에서는 클래스의 기초부터 고급 기법까지 실전 예제와 함께 완벽하게 알려드립니다.
클래스란 무엇인가?
클래스의 개념
클래스는 서로 연관 있는 변수(데이터)와 함수(기능)를 하나로 묶어 이름을 붙인 것입니다.
현실 세계의 개념을 코드로 표현할 때 유용합니다.
# 예시: 자동차를 클래스로 표현
class Car:
"""자동차 클래스"""
def __init__(self, brand, model):
self.brand = brand # 브랜드 (데이터)
self.model = model # 모델명 (데이터)
self.speed = 0 # 현재 속도 (데이터)
def accelerate(self): # 가속 (기능)
self.speed += 10
print(f"현재 속도: {self.speed}km/h")
def brake(self): # 브레이크 (기능)
self.speed = 0
print("정지했습니다")
# 클래스 사용
my_car = Car("현대", "소나타")
my_car.accelerate() # 출력: 현재 속도: 10km/h
my_car.accelerate() # 출력: 현재 속도: 20km/h
my_car.brake() # 출력: 정지했습니다
클래스 vs 함수
언제 클래스를 사용하고, 언제 함수를 사용할까요?
# 함수 방식 (단순한 경우)
def calculate_area(width, height):
return width * height
area = calculate_area(10, 5)
# 클래스 방식 (복잡한 경우)
class Rectangle:
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)
def resize(self, width, height):
self.width = width
self.height = height
rect = Rectangle(10, 5)
print(rect.area()) # 50
print(rect.perimeter()) # 30
rect.resize(20, 10)
print(rect.area()) # 200
클래스를 사용해야 하는 경우
- 관련된 데이터와 기능이 여러 개일 때
- 상태(state)를 유지해야 할 때
- 같은 구조의 객체를 여러 개 만들 때
- 코드를 재사용하고 확장해야 할 때
클래스 정의하기
기본 문법
class ClassName:
"""클래스 설명 (docstring)"""
pass # 내용이 없을 때
클래스 이름 규칙
- 첫 글자는 대문자로 시작 (PascalCase)
- 여러 단어는 첫 글자를 대문자로 (예: MyClass, UserAccount)
- 언더스코어는 사용하지 않음 (my_class ❌, MyClass ✅)
# 올바른 클래스 이름
class Person:
pass
class BankAccount:
pass
class HTTPRequest:
pass
# 잘못된 클래스 이름 (동작은 하지만 권장하지 않음)
class person: # 소문자 시작
pass
class bank_account: # 언더스코어 사용
pass
인스턴스 생성
클래스로부터 **인스턴스(객체)**를 생성합니다.
class Dog:
pass
# 인스턴스 생성
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()
# 각 인스턴스는 독립적
print(dog1) # <__main__.Dog object at 0x...>
print(dog2) # <__main__.Dog object at 0x...> (다른 주소)
print(dog1 == dog2) # False (서로 다른 객체)
생성자: init() 메서드
init()이란?
__init__()은 **생성자(Constructor)**로, 인스턴스가 생성될 때 자동으로 호출됩니다.
초기값 설정, 필요한 준비 작업 등을 수행합니다.
class Person:
def __init__(self, name, age):
"""생성자: 인스턴스 초기화"""
print(f"{name}님의 인스턴스가 생성되었습니다")
self.name = name
self.age = age
# 인스턴스 생성 시 자동으로 __init__ 호출
person1 = Person("김철수", 25)
# 출력: 김철수님의 인스턴스가 생성되었습니다
print(person1.name) # 김철수
print(person1.age) # 25
self의 의미
self는 인스턴스 자기 자신을 가리키는 참조입니다.
class Counter:
def __init__(self):
self.count = 0 # self.count는 이 인스턴스의 count 변수
def increment(self):
self.count += 1 # 자기 자신의 count를 증가
def get_count(self):
return self.count # 자기 자신의 count를 반환
counter1 = Counter()
counter2 = Counter()
counter1.increment()
counter1.increment()
print(counter1.get_count()) # 2
print(counter2.get_count()) # 0 (독립적)
왜 self를 명시해야 할까?
Python의 설계 철학인 “명시적이 암시적보다 낫다(Explicit is better than implicit)”를 따릅니다.
class Example:
def method(self):
# self를 통해 명시적으로 인스턴스 변수임을 표시
self.value = 10
# 지역 변수
local_value = 20
print(self.value) # 인스턴스 변수: 10
print(local_value) # 지역 변수: 20
기본값 설정
매개변수에 기본값을 지정할 수 있습니다.
class Student:
def __init__(self, name, grade=1, school="미정"):
self.name = name
self.grade = grade
self.school = school
# 다양한 방식으로 생성 가능
student1 = Student("김철수")
student2 = Student("이영희", 2)
student3 = Student("박민수", 3, "서울고등학교")
print(student1.grade) # 1 (기본값)
print(student2.grade) # 2
print(student3.school) # 서울고등학교
인스턴스 변수 vs 클래스 변수
인스턴스 변수
각 인스턴스가 독립적으로 가지는 변수입니다.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # 인스턴스 변수
self.balance = balance # 인스턴스 변수
def deposit(self, amount):
self.balance += amount
account1 = BankAccount("김철수", 10000)
account2 = BankAccount("이영희", 50000)
account1.deposit(5000)
print(account1.balance) # 15000
print(account2.balance) # 50000 (독립적)
클래스 변수
모든 인스턴스가 공유하는 변수입니다.
class Minsu:
# 클래스 변수 (모든 인스턴스가 공유)
version = "v4"
count = 0 # 생성된 인스턴스 개수
def __init__(self, name="Minsu"):
# 인스턴스 변수 (각 인스턴스마다 독립적)
self.name = name
Minsu.count += 1 # 클래스 변수 증가
# 인스턴스 생성
voice1 = Minsu("김철수")
voice2 = Minsu("이영희")
voice3 = Minsu("박민수")
# 클래스 변수는 모든 인스턴스가 공유
print(voice1.version) # v4
print(voice2.version) # v4
print(voice3.version) # v4
# 클래스 변수 변경 시 모든 인스턴스에 영향
Minsu.version = "v5"
print(voice1.version) # v5
print(voice2.version) # v5
print(voice3.version) # v5
# 생성된 인스턴스 개수 확인
print(Minsu.count) # 3
클래스 변수 vs 인스턴스 변수 비교
class Car:
# 클래스 변수
wheels = 4 # 모든 자동차는 바퀴 4개
total_cars = 0 # 생성된 자동차 총 개수
def __init__(self, brand, color):
# 인스턴스 변수
self.brand = brand # 각 자동차의 브랜드
self.color = color # 각 자동차의 색상
Car.total_cars += 1
car1 = Car("현대", "흰색")
car2 = Car("기아", "검정")
# 클래스 변수 접근
print(Car.wheels) # 4
print(car1.wheels) # 4 (인스턴스를 통해서도 접근 가능)
print(Car.total_cars) # 2
# 인스턴스 변수 접근
print(car1.brand) # 현대
print(car2.brand) # 기아
# print(Car.brand) # ❌ AttributeError (클래스는 인스턴스 변수에 접근 불가)
메서드(Method)
클래스 내부의 함수를 메서드라고 합니다.
인스턴스 메서드
가장 일반적인 메서드로, 첫 번째 매개변수로 self를 받습니다.
class Minsu:
version = "v4"
def __init__(self, name="Minsu"):
self.name = name
def song(self, title="hello"):
"""노래 부르기 메서드"""
print(f"{self.name} sing the {title}")
def medley(self):
"""여러 곡 이어 부르기"""
self.song() # 기본값으로 호출
self.song("second song")
self.song("third song")
# 사용 예시
voice_minsu = Minsu("김철수")
voice_minsu.song() # 김철수 sing the hello
voice_minsu.song("hello world") # 김철수 sing the hello world
voice_minsu.medley()
# 출력:
# 김철수 sing the hello
# 김철수 sing the second song
# 김철수 sing the third song
메서드 체이닝
메서드가 self를 반환하면 메서드를 연속으로 호출할 수 있습니다.
class Calculator:
def __init__(self):
self.value = 0
def add(self, num):
self.value += num
return self # 자기 자신 반환
def subtract(self, num):
self.value -= num
return self
def multiply(self, num):
self.value *= num
return self
def result(self):
return self.value
# 메서드 체이닝
calc = Calculator()
result = calc.add(10).multiply(2).subtract(5).result()
print(result) # 15
# 위 코드는 다음과 같음
# calc.add(10) # value = 10
# calc.multiply(2) # value = 20
# calc.subtract(5) # value = 15
# calc.result() # 15
Getter와 Setter
class Person:
def __init__(self, name, age):
self._name = name # _ 접두사: 내부에서만 사용 권장
self._age = age
# Getter
def get_name(self):
return self._name
def get_age(self):
return self._age
# Setter
def set_age(self, age):
if age < 0:
print("나이는 0보다 작을 수 없습니다")
return
self._age = age
person = Person("김철수", 25)
print(person.get_name()) # 김철수
print(person.get_age()) # 25
person.set_age(26)
print(person.get_age()) # 26
person.set_age(-5) # 나이는 0보다 작을 수 없습니다
print(person.get_age()) # 26 (변경되지 않음)
상속(Inheritance)
상속의 개념
기존 클래스의 기능을 물려받아 확장할 수 있습니다.
# 부모 클래스 (슈퍼 클래스, 기반 클래스)
class Player:
version = "v4"
def __init__(self, name):
self.name = name
def song(self, title="default song"):
print(f"{self.name} is playing {title}")
# 자식 클래스 (서브 클래스, 파생 클래스)
class Tottenham(Player):
def __init__(self, uniform="class uniform"):
self.uniform = uniform
# 부모 클래스의 __init__ 호출
super().__init__("tottenham")
def shoot(self):
print("Shooting!")
# 사용 예시
team = Tottenham()
print(team.uniform) # class uniform
print(team.version) # v4 (부모 클래스의 클래스 변수)
print(team.name) # tottenham (부모 클래스의 인스턴스 변수)
team.shoot() # Shooting! (자식 클래스의 메서드)
team.song("hello world") # tottenham is playing hello world (부모 클래스의 메서드)
super() 이해하기
super()는 부모 클래스에 접근할 수 있게 해줍니다.
class Animal:
def __init__(self, name):
self.name = name
print(f"Animal 생성: {name}")
def speak(self):
print("동물이 소리를 냅니다")
class Dog(Animal):
def __init__(self, name, breed):
# 부모 클래스의 __init__ 호출
super().__init__(name)
self.breed = breed
print(f"Dog 생성: {breed}")
def speak(self):
# 부모 클래스의 메서드 호출
super().speak()
print("멍멍!")
# 인스턴스 생성
dog = Dog("바둑이", "진돗개")
# 출력:
# Animal 생성: 바둑이
# Dog 생성: 진돗개
print(dog.name) # 바둑이
print(dog.breed) # 진돗개
dog.speak()
# 출력:
# 동물이 소리를 냅니다
# 멍멍!
메서드 오버라이딩
자식 클래스에서 부모 클래스의 메서드를 재정의할 수 있습니다.
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0
def describe(self):
print(f"이것은 {self.name}입니다")
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("직사각형")
self.width = width
self.height = height
# 메서드 오버라이딩
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
super().__init__("원")
self.radius = radius
# 메서드 오버라이딩
def area(self):
return 3.14 * self.radius ** 2
# 사용
rect = Rectangle(10, 5)
circle = Circle(7)
rect.describe() # 이것은 직사각형입니다
print(rect.area()) # 50
circle.describe() # 이것은 원입니다
print(circle.area()) # 153.86
다중 상속
Python은 여러 클래스를 동시에 상속받을 수 있습니다.
class Flyable:
def fly(self):
print("날고 있습니다")
class Swimmable:
def swim(self):
print("수영하고 있습니다")
class Duck(Flyable, Swimmable):
def __init__(self, name):
self.name = name
def quack(self):
print("꽥꽥!")
# 오리는 날 수도 있고 수영도 할 수 있음
duck = Duck("도널드")
duck.fly() # 날고 있습니다
duck.swim() # 수영하고 있습니다
duck.quack() # 꽥꽥!
실전 예제
예제 1: 은행 계좌 시스템
class BankAccount:
"""은행 계좌 클래스"""
bank_name = "파이썬은행" # 클래스 변수
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = [] # 거래 내역
def deposit(self, amount):
"""입금"""
if amount <= 0:
print("입금액은 0보다 커야 합니다")
return
self.balance += amount
self.transactions.append(f"입금: +{amount:,}원")
print(f"{amount:,}원이 입금되었습니다. 잔액: {self.balance:,}원")
def withdraw(self, amount):
"""출금"""
if amount <= 0:
print("출금액은 0보다 커야 합니다")
return
if amount > self.balance:
print("잔액이 부족합니다")
return
self.balance -= amount
self.transactions.append(f"출금: -{amount:,}원")
print(f"{amount:,}원이 출금되었습니다. 잔액: {self.balance:,}원")
def show_balance(self):
"""잔액 조회"""
print(f"{self.owner}님의 잔액: {self.balance:,}원")
def show_transactions(self):
"""거래 내역 조회"""
print(f"\\\\n=== {self.owner}님의 거래 내역 ===")
for transaction in self.transactions:
print(transaction)
print(f"현재 잔액: {self.balance:,}원\\\\n")
# 사용 예시
account = BankAccount("김철수", 100000)
account.deposit(50000)
account.withdraw(30000)
account.deposit(20000)
account.show_balance()
account.show_transactions()
예제 2: 학생 관리 시스템
class Student:
"""학생 클래스"""
school_name = "파이썬고등학교"
def __init__(self, name, student_id, grade):
self.name = name
self.student_id = student_id
self.grade = grade
self.scores = {} # 과목별 점수
def add_score(self, subject, score):
"""성적 추가"""
if score < 0 or score > 100:
print("점수는 0~100 사이여야 합니다")
return
self.scores[subject] = score
print(f"{subject}: {score}점 입력 완료")
def get_average(self):
"""평균 점수 계산"""
if not self.scores:
return 0
return sum(self.scores.values()) / len(self.scores)
def show_report(self):
"""성적표 출력"""
print(f"\\\\n=== {self.name} 학생 성적표 ===")
print(f"학번: {self.student_id}")
print(f"학년: {self.grade}학년")
print("\\\\n과목별 성적:")
for subject, score in self.scores.items():
print(f" {subject}: {score}점")
avg = self.get_average()
print(f"\\\\n평균: {avg:.1f}점")
# 등급 판정
if avg >= 90:
grade = "A"
elif avg >= 80:
grade = "B"
elif avg >= 70:
grade = "C"
elif avg >= 60:
grade = "D"
else:
grade = "F"
print(f"등급: {grade}\\\\n")
# 사용 예시
student = Student("김철수", "2025001", 2)
student.add_score("국어", 85)
student.add_score("영어", 90)
student.add_score("수학", 88)
student.add_score("과학", 92)
student.show_report()
예제 3: 게임 캐릭터 시스템
class Character:
"""게임 캐릭터 기본 클래스"""
def __init__(self, name, hp, attack):
self.name = name
self.hp = hp
self.max_hp = hp
self.attack = attack
self.level = 1
def take_damage(self, damage):
"""피해 받기"""
self.hp -= damage
if self.hp < 0:
self.hp = 0
print(f"{self.name}이(가) {damage} 피해를 입었습니다. (HP: {self.hp}/{self.max_hp})")
if self.hp == 0:
print(f"{self.name}이(가) 쓰러졌습니다!")
def heal(self, amount):
"""회복"""
self.hp += amount
if self.hp > self.max_hp:
self.hp = self.max_hp
print(f"{self.name}이(가) {amount} 회복했습니다. (HP: {self.hp}/{self.max_hp})")
def attack_enemy(self, enemy):
"""적 공격"""
print(f"{self.name}이(가) {enemy.name}을(를) 공격합니다!")
enemy.take_damage(self.attack)
class Warrior(Character):
"""전사 클래스"""
def __init__(self, name):
super().__init__(name, hp=150, attack=25)
self.defense = 10
def special_attack(self, enemy):
"""특수 공격: 강타"""
damage = self.attack * 2
print(f"{self.name}의 강타!")
enemy.take_damage(damage)
class Mage(Character):
"""마법사 클래스"""
def __init__(self, name):
super().__init__(name, hp=80, attack=35)
self.mana = 100
def special_attack(self, enemy):
"""특수 공격: 파이어볼"""
if self.mana < 30:
print("마나가 부족합니다!")
return
self.mana -= 30
damage = self.attack * 1.5
print(f"{self.name}의 파이어볼! (마나: {self.mana})")
enemy.take_damage(int(damage))
# 사용 예시
warrior = Warrior("전사왕")
mage = Mage("대마법사")
print("=== 전투 시작 ===")
warrior.attack_enemy(mage)
mage.special_attack(warrior)
warrior.special_attack(mage)
특수 메서드 (매직 메서드)
__로 시작하고 끝나는 특수한 메서드들입니다.
str()과 repr()
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
"""사용자 친화적인 문자열 (print 시 출력)"""
return f"{self.name} ({self.price:,}원)"
def __repr__(self):
"""개발자용 문자열 (디버깅용)"""
return f"Product(name='{self.name}', price={self.price})"
product = Product("노트북", 1500000)
print(product) # 노트북 (1,500,000원) (__str__)
print(repr(product)) # Product(name='노트북', price=1500000) (__repr__)
len(), getitem()
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __len__(self):
"""len() 함수 지원"""
return len(self.songs)
def __getitem__(self, index):
"""인덱싱 지원"""
return self.songs[index]
playlist = Playlist("내 플레이리스트")
playlist.add_song("노래1")
playlist.add_song("노래2")
playlist.add_song("노래3")
print(len(playlist)) # 3
print(playlist[0]) # 노래1
print(playlist[1]) # 노래2
# for 루프도 가능
for song in playlist:
print(song)
연산자 오버로딩
class Vector:
def __init__(self, x, y):
self.x = x
self.y = 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 __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(5, 7)
v3 = v1 + v2
print(v3) # Vector(7, 10)
v4 = v2 - v1
print(v4) # Vector(3, 4)
v5 = v1 * 3
print(v5) # Vector(6, 9)
고급 기능
클래스 메서드 (@classmethod)
클래스 자체를 첫 번째 인자로 받는 메서드입니다.
class Person:
population = 0 # 클래스 변수
def __init__(self, name, age):
self.name = name
self.age = age
Person.population += 1
@classmethod
def get_population(cls):
"""클래스 메서드: 전체 인구 수 반환"""
return cls.population
@classmethod
def from_birth_year(cls, name, birth_year):
"""대체 생성자: 출생년도로 인스턴스 생성"""
from datetime import datetime
age = datetime.now().year - birth_year
return cls(name, age)
# 일반 생성자
person1 = Person("김철수", 25)
# 클래스 메서드로 생성
person2 = Person.from_birth_year("이영희", 1995)
# 클래스 메서드 호출
print(Person.get_population()) # 2
print(person1.get_population()) # 2 (인스턴스로도 호출 가능)
정적 메서드 (@staticmethod)
클래스나 인스턴스와 무관한 독립적인 메서드입니다.
class MathUtil:
"""수학 유틸리티 클래스"""
@staticmethod
def is_even(num):
"""짝수 판별"""
return num % 2 == 0
@staticmethod
def is_prime(num):
"""소수 판별"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
@staticmethod
def factorial(n):
"""팩토리얼 계산"""
if n <= 1:
return 1
return n * MathUtil.factorial(n - 1)
# 인스턴스 생성 없이 바로 사용
print(MathUtil.is_even(10)) # True
print(MathUtil.is_prime(17)) # True
print(MathUtil.factorial(5)) # 120
프로퍼티 (@property)
메서드를 속성처럼 사용할 수 있게 해줍니다.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""반지름 getter"""
return self._radius
@radius.setter
def radius(self, value):
"""반지름 setter"""
if value < 0:
raise ValueError("반지름은 0보다 커야 합니다")
self._radius = value
@property
def area(self):
"""넓이 (읽기 전용)"""
return 3.14 * self._radius ** 2
@property
def circumference(self):
"""둘레 (읽기 전용)"""
return 2 * 3.14 * self._radius
# 사용 예시
circle = Circle(5)
# 속성처럼 접근
print(circle.radius) # 5
print(circle.area) # 78.5
print(circle.circumference) # 31.4
# setter를 통한 값 변경
circle.radius = 10
print(circle.area) # 314.0
# circle.radius = -5 # ValueError 발생
Private 변수 (캡슐화)
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
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)
# 정상 접근
print(account.get_balance()) # 10000
account.deposit(5000)
print(account.get_balance()) # 15000
# 직접 접근 시도 (실패)
# print(account.__balance) # AttributeError
# 네임 맹글링으로 접근은 가능하지만 권장하지 않음
# print(account._BankAccount__balance)
추상 클래스 (ABC)
구현을 강제하는 추상 클래스를 만들 수 있습니다.
from abc import ABC, abstractmethod
class Shape(ABC):
"""도형 추상 클래스"""
@abstractmethod
def area(self):
"""넓이 계산 (반드시 구현해야 함)"""
pass
@abstractmethod
def perimeter(self):
"""둘레 계산 (반드시 구현해야 함)"""
pass
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.14 * self.radius ** 2
def perimeter(self):
return 2 * 3.14 * self.radius
# 사용
rect = Rectangle(10, 5)
circle = Circle(7)
print(rect.area()) # 50
print(circle.area()) # 153.86
# shape = Shape() # TypeError: 추상 클래스는 인스턴스화 불가
디자인 패턴 예제
싱글톤 패턴
하나의 인스턴스만 생성되도록 보장합니다.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
self.value = 0
# 여러 번 생성해도 같은 인스턴스
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
s1.value = 100
print(s2.value) # 100 (같은 인스턴스)
팩토리 패턴
객체 생성을 별도의 메서드로 분리합니다.
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "멍멍!"
class Cat(Animal):
def speak(self):
return "야옹~"
class Bird(Animal):
def speak(self):
return "짹짹!"
class AnimalFactory:
"""동물 생성 팩토리"""
@staticmethod
def create_animal(animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
elif animal_type == "bird":
return Bird()
else:
raise ValueError(f"알 수 없는 동물 타입: {animal_type}")
# 사용
factory = AnimalFactory()
dog = factory.create_animal("dog")
cat = factory.create_animal("cat")
bird = factory.create_animal("bird")
print(dog.speak()) # 멍멍!
print(cat.speak()) # 야옹~
print(bird.speak()) # 짹짹!
실무 활용 팁
1. 클래스 문서화
class User:
"""
사용자 정보를 관리하는 클래스
Attributes:
username (str): 사용자 이름
email (str): 이메일 주소
age (int): 나이
Examples:
>>> user = User("john", "john@example.com", 25)
>>> print(user.username)
john
"""
def __init__(self, username, email, age):
"""
User 인스턴스를 초기화합니다.
Args:
username (str): 사용자 이름
email (str): 이메일 주소
age (int): 나이
Raises:
ValueError: age가 0보다 작을 경우
"""
if age < 0:
raise ValueError("나이는 0보다 커야 합니다")
self.username = username
self.email = email
self.age = age
def update_email(self, new_email):
"""
이메일 주소를 업데이트합니다.
Args:
new_email (str): 새로운 이메일 주소
Returns:
bool: 업데이트 성공 여부
"""
if "@" not in new_email:
return False
self.email = new_email
return True
2. 타입 힌팅
from typing import List, Optional
class Student:
def __init__(self, name: str, age: int, grades: List[int]):
self.name: str = name
self.age: int = age
self.grades: List[int] = grades
def get_average(self) -> float:
"""평균 점수를 반환합니다."""
if not self.grades:
return 0.0
return sum(self.grades) / len(self.grades)
def find_max_grade(self) -> Optional[int]:
"""최고 점수를 반환합니다."""
if not self.grades:
return None
return max(self.grades)
3. 데이터 클래스 (@dataclass)
간단한 데이터 저장용 클래스를 쉽게 만들 수 있습니다.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Product:
name: str
price: int
quantity: int = 0
tags: List[str] = field(default_factory=list)
def total_value(self) -> int:
return self.price * self.quantity
# 사용
product = Product("노트북", 1500000, 5, ["전자제품", "컴퓨터"])
print(product) # Product(name='노트북', price=1500000, quantity=5, ...)
print(product.total_value()) # 7500000
자주 하는 실수와 해결법
실수 1: self 빠뜨리기
class Wrong:
def method(self):
value = 10 # 지역 변수
# self.value = 10 # 인스턴스 변수 (올바름)
def another_method(self):
# print(value) # NameError!
print(self.value) # 이렇게 해야 함
# ✅ 올바른 예
class Correct:
def __init__(self):
self.value = 10 # 인스턴스 변수로 저장
def method(self):
print(self.value) # 정상 작동
실수 2: 가변 객체를 기본값으로 사용
# ❌ 잘못된 예
class Wrong:
def __init__(self, items=[]): # 위험!
self.items = items
w1 = Wrong()
w2 = Wrong()
w1.items.append(1)
print(w2.items) # [1] - 공유됨!
# ✅ 올바른 예
class Correct:
def __init__(self, items=None):
self.items = items if items is not None else []
c1 = Correct()
c2 = Correct()
c1.items.append(1)
print(c2.items) # [] - 독립적
실수 3: super() 호출 잊기
# ❌ 잘못된 예
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("김철수", 10)
# print(child.name) # AttributeError!
# ✅ 올바른 예
class ChildCorrect(Parent):
def __init__(self, name, age):
super().__init__(name) # 부모 초기화
self.age = age
child = ChildCorrect("김철수", 10)
print(child.name) # 김철수
성능 최적화
slots 사용
메모리 사용량을 줄이고 속성 접근 속도를 높입니다.
# 일반 클래스
class NormalClass:
def __init__(self, x, y):
self.x = x
self.y = y
# __slots__ 사용
class SlottedClass:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# 메모리 사용량 비교
import sys
normal = NormalClass(1, 2)
slotted = SlottedClass(1, 2)
print(sys.getsizeof(normal.__dict__)) # 약 112 bytes
print(sys.getsizeof(slotted)) # 약 56 bytes (절반)
# 주의: __slots__ 사용 시 동적 속성 추가 불가
# slotted.z = 3 # AttributeError
테스트 작성
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("0으로 나눌 수 없습니다")
return a / b
# 단위 테스트
import unittest
class TestCalculator(unittest.TestCase):
def setUp(self):
"""각 테스트 전에 실행"""
self.calc = Calculator()
def test_add(self):
self.assertEqual(self.calc.add(2, 3), 5)
self.assertEqual(self.calc.add(-1, 1), 0)
def test_divide(self):
self.assertEqual(self.calc.divide(10, 2), 5)
with self.assertRaises(ValueError):
self.calc.divide(10, 0)
# 테스트 실행
# unittest.main()
마무리하며
Python 클래스는 코드를 구조화하고 재사용성을 높이는 강력한 도구입니다.
핵심 요약
- 클래스: 관련된 데이터와 기능을 묶은 설계도
- 인스턴스: 클래스로부터 생성된 실제 객체
- self: 인스턴스 자기 자신을 가리키는 참조
- init(): 인스턴스 초기화 생성자
- 상속: 기존 클래스의 기능을 확장
- 캡슐화: 내부 구현을 숨기고 인터페이스만 노출
학습 로드맵
- 기본 클래스 정의와 인스턴스 생성
- 메서드 작성과 self 이해
- 생성자(init)와 인스턴스 변수
- 클래스 변수와 인스턴스 변수 차이
- 상속과 메서드 오버라이딩
- 특수 메서드와 연산자 오버로딩
- 프로퍼티, 클래스 메서드, 정적 메서드
- 추상 클래스와 디자인 패턴
실무 적용 팁
- 작은 단위로 클래스 설계 (단일 책임 원칙)
- 적절한 캡슐화로 내부 구현 숨기기
- 문서화와 타입 힌팅으로 가독성 높이기
- 테스트 코드 작성으로 안정성 확보
클래스는 처음에는 어렵게 느껴질 수 있지만, 실제 프로젝트에 적용하면서 자연스럽게 익숙해집니다. 작은 예제부터 시작해서 점차 복잡한 시스템을 설계해보세요!
참고 자료
- Python 공식 문서 – 클래스: https://docs.python.org/ko/3/tutorial/classes.html
- Python 공식 문서 – 데이터 모델: https://docs.python.org/ko/3/reference/datamodel.html
- Real Python – OOP in Python: https://realpython.com/python3-object-oriented-programming/
관련 글
- Python 상속과 다형성 완벽 가이드: 고급 객체지향 프로그래밍
- Python 디자인 패턴: 실무에서 자주 쓰이는 10가지 패턴
- Python 데이터 클래스(@dataclass) 활용법: 간결한 클래스 작성