PythonPython 기초 · 7입문

클래스와 객체 — 데이터와 기능을 하나로 묶기

Python클래스객체OOPPydantic

왜 클래스가 필요한가?

사용자 정보를 딕셔너리로 관리하면:

user = {"name": "철수", "age": 25, "email": "kim@example.com"}

# 나이를 업데이트하는 함수를 별도로 관리해야 함
def update_age(user, new_age):
    if new_age < 0:
        raise ValueError("나이는 0 이상")
    user["age"] = new_age

클래스를 쓰면 데이터와 관련 기능을 하나로 묶을 수 있습니다.


클래스 기본 구조

class User:
    # __init__: 객체 생성 시 자동 실행
    def __init__(self, name: str, age: int, email: str):
        self.name = name      # 인스턴스 속성
        self.age = age
        self.email = email
    
    # 메서드: 클래스에 속한 함수
    def greet(self):
        return f"안녕하세요, {self.name}입니다!"
    
    def update_age(self, new_age: int):
        if new_age < 0:
            raise ValueError("나이는 0 이상이어야 합니다.")
        self.age = new_age
    
    def __str__(self):  # print() 할 때 출력되는 문자열
        return f"User(name={self.name}, age={self.age})"

# 객체 생성
user1 = User("철수", 25, "kim@example.com")
user2 = User("영희", 30, "lee@example.com")

print(user1.greet())    # 안녕하세요, 철수입니다!
print(user2.name)       # 영희
user1.update_age(26)
print(user1)            # User(name=철수, age=26)

클래스 vs 인스턴스

flowchart LR
    CLASS["클래스 (설계도)\nclass User"] -->|"인스턴스화"| I1["객체 1\nuser1 = User('철수', 25)"]
    CLASS -->|"인스턴스화"| I2["객체 2\nuser2 = User('영희', 30)"]
    CLASS -->|"인스턴스화"| I3["객체 N\n..."]

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

class Counter:
    total_created = 0   # 클래스 변수: 모든 인스턴스 공유
    
    def __init__(self, name: str):
        self.name = name    # 인스턴스 변수: 각 객체마다 별도
        Counter.total_created += 1
    
    @classmethod
    def get_total(cls) -> int:
        return cls.total_created

c1 = Counter("A")
c2 = Counter("B")
print(Counter.get_total())   # 2

상속: 기존 클래스를 확장하기

class Animal:
    def __init__(self, name: str):
        self.name = name
    
    def speak(self) -> str:
        return "..."
    
    def __str__(self):
        return f"{self.__class__.__name__}({self.name})"

class Dog(Animal):           # Animal을 상속
    def speak(self) -> str:  # 메서드 오버라이드
        return "멍멍!"
    
    def fetch(self):
        return f"{self.name}이(가) 공을 가져왔습니다!"

class Cat(Animal):
    def speak(self) -> str:
        return "야옹~"

dog = Dog("바둑이")
cat = Cat("나비")

print(dog.speak())   # 멍멍!
print(cat.speak())   # 야옹~
print(dog.fetch())   # 바둑이이(가) 공을 가져왔습니다!
print(dog)           # Dog(바둑이)

실전: LLM 대화 관리 클래스

from datetime import datetime
from openai import OpenAI

class Conversation:
    def __init__(self, system_prompt: str = "당신은 친절한 어시스턴트입니다."):
        self.client = OpenAI()
        self.messages = [{"role": "system", "content": system_prompt}]
        self.created_at = datetime.now()
        self.turn_count = 0
    
    def chat(self, user_message: str) -> str:
        self.messages.append({"role": "user", "content": user_message})
        
        response = self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=self.messages
        )
        
        assistant_message = response.choices[0].message.content
        self.messages.append({"role": "assistant", "content": assistant_message})
        self.turn_count += 1
        
        return assistant_message
    
    def clear(self):
        system = self.messages[0]  # 시스템 프롬프트 유지
        self.messages = [system]
        self.turn_count = 0
    
    def get_history(self) -> list:
        return [m for m in self.messages if m["role"] != "system"]
    
    def __len__(self):
        return self.turn_count
    
    def __str__(self):
        return f"Conversation({self.turn_count}턴, {self.created_at.strftime('%H:%M')}시작)"

# 사용
conv = Conversation("당신은 파이썬 튜터입니다.")
print(conv.chat("리스트 컴프리헨션이 뭔가요?"))
print(conv.chat("예시 코드 보여주세요."))
print(f"현재 대화: {conv}")  # Conversation(2턴, 14:30시작)
print(f"대화 수: {len(conv)}")  # 2

Pydantic: 데이터 검증 클래스

LLM API 개발에서 자주 쓰이는 Pydantic을 이해하려면 클래스 개념이 필요합니다.

from pydantic import BaseModel, Field, validator
from typing import Optional, List

class UserProfile(BaseModel):
    name: str
    age: int = Field(ge=0, le=150)      # 0 이상 150 이하
    email: str
    skills: List[str] = []              # 기본값 빈 리스트
    bio: Optional[str] = None
    
    @validator("email")
    def validate_email(cls, v):
        if "@" not in v:
            raise ValueError("유효한 이메일 주소를 입력하세요.")
        return v.lower()

# 유효한 데이터
user = UserProfile(name="철수", age=25, email="Kim@Example.COM")
print(user.email)    # kim@example.com (자동 소문자)
print(user.skills)   # []

# 잘못된 데이터
try:
    bad_user = UserProfile(name="영희", age=-1, email="invalid")
except Exception as e:
    print(e)

매직 메서드 (Dunder Methods)

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):   # v1 + v2
        return Vector(self.x + other.x, self.y + other.y)
    
    def __str__(self):          # print(v)
        return f"Vector({self.x}, {self.y})"
    
    def __len__(self):          # len(v)
        return 2
    
    def __eq__(self, other):    # v1 == v2
        return self.x == other.x and self.y == other.y

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)   # Vector(4, 6)
print(len(v1))   # 2

정리

개념설명
class자료형 설계도 정의
__init__객체 생성 시 자동 실행 초기화 메서드
self현재 인스턴스를 가리키는 참조
상속class Child(Parent)로 기능 확장
@classmethod인스턴스 없이 호출 가능한 메서드
Pydantic클래스 기반 데이터 검증 라이브러리

다음 편에서는 외부 패키지와 가상환경 — pip로 패키지를 설치하고 프로젝트별 환경을 격리하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.