[Python TIL] 함수(Function)와 클래스(Class)
함수 → 클래스로 가는 사고 전환
함수는 "동작"만 담당했지만,
어떤 대상(데이터)과 관련된 동작이 많아지면
"이 데이터와 함수들을 한 덩어리로 묶는 것이 좋다!"
→ 이게 바로 클래스(class) 개념!
예:
class TextCleaner:
def __init__(self, text):
self.text = text
def clean(self):
return self.text.lower().strip()
이것이 **객체지향 프로그래밍(OOP)**의 시작점이다!
자주 묻는 질문 (FAQ)
| 질문 | 답변 |
| 함수 하나만 만들면 되나요? | NO! 작업 단위를 작게 쪼개서 여러 개의 함수를 만들어 조립하는 게 좋다. |
| 함수 이름은 어떻게 짓나요? | 무슨 일을 하는지 동사 중심으로: clean_text(), calculate_avg() 등 |
함수로 부족할 때, 클래스가 필요해지는 순간들
함수는 프로그래밍에서 굉장히 강력한 도구이다. 어떤 **동작(기능)**을 여러 번 반복해서 사용하고 싶을 때, 우리는 자연스럽게 함수를 만든다.
그런데 실무를 하다 보면 단순한 기능만으론 부족한 경우가 많다. 바로, "상태(state)"도 함께 관리해야 하는 상황이다.
✅ 함수는 ‘기능 도구 상자’다
예를 들어, 은행 계좌의 입금과 출금을 처리하는 함수를 만든다고 해보자.
def deposit(balance, amount):
return balance + amount
def withdraw(balance, amount):
return balance - amount
겉보기엔 멀쩡해 보인다. 하지만…
매번
balance를 인자로 넘겨줘야 하고,계좌가 여러 개 생기면
balance1,balance2,balance3… 복잡도가 올라간다.
이처럼 함수는 ‘동작’은 잘 처리하지만, 데이터를 기억하지 못한다는 한계가 있다.
🧠 비유: 함수 vs 클래스
| 구분 | 비유 | 설명 |
| 함수 | 드라이버 하나하나가 담긴 도구 상자 | 필요한 기능을 하나씩 꺼내 쓰는 구조 |
| 클래스 | 다양한 기능과 설정을 포함한 전동 공구 키트 | 데이터도 기억하고 여러 기능을 조합할 수 있음 |
함수는 필요한 도구를 꺼내 쓰는 데 유용하지만, 상황이 복잡해질수록 ‘설정’을 기억하고, 다양한 동작을 함께 다루는 클래스가 훨씬 편리하다.
✅ 실무에서 마주치는 클래스의 필요성
🔹 예시 1: 데이터 전처리기를 만들어야 할 때
def clean_text(text):
return text.lower().replace(",", "")
간단하고 좋아 보이지만, 문제가 있다.
전처리 옵션이 사용자마다 달라질 때는?
대량의 데이터를 한 번에 처리해야 할 때는?
→ 함수 하나만으로는 처리 옵션을 일일이 넘기기 불편하고, 유지보수가 어려워진다.
🔧 클래스 도입
class TextCleaner:
def __init__(self, lower=True, remove_punc=True):
self.lower = lower
self.remove_punc = remove_punc
def clean(self, text):
if self.lower:
text = text.lower()
if self.remove_punc:
text = text.replace(",", "")
return text
cleaner1 = TextCleaner(lower=True, remove_punc=True)
cleaner2 = TextCleaner(lower=False)
print(cleaner1.clean("Hello, World!")) # hello world!
print(cleaner2.clean("Hello, World!")) # Hello world!
➡️ 각 전처리기는 자신의 상태를 기억하며 동작한다.
➡️ 마치 ‘설정 가능한 기계’를 쓰는 느낌이다.
🔹 예시 2: 모델 평가기를 관리할 때
def evaluate(preds, labels):
return sum([p == l for p, l in zip(preds, labels)]) / len(labels)
정확도(accuracy)만 측정할 땐 좋지만, F1, AUC, Precision 등 다양한 평가 지표를 써야 할 경우 함수가 기하급수적으로 늘어난다.
→ 함수만으로는 지표와 평가 데이터를 구조적으로 관리하기 어려워진다.
🔧 클래스 도입
class Evaluator:
def __init__(self, metric="accuracy"):
self.metric = metric
def evaluate(self, preds, labels):
if self.metric == "accuracy":
return sum(p == l for p, l in zip(preds, labels)) / len(labels)
elif self.metric == "f1":
return self._f1_score(preds, labels)
# 더 많은 지표 추가 가능
else:
raise ValueError("Unsupported metric")
def _f1_score(self, preds, labels):
# F1 계산 로직 (생략)
return 0.85
acc_evaluator = Evaluator(metric="accuracy")
f1_evaluator = Evaluator(metric="f1")
➡️ Evaluator는 평가 지표라는 상태를 기억하고, 거기에 맞는 방식으로 평가를 수행한다.
클래스 선언


✅ 정리: 함수에서 클래스가 필요한 이유
| 문제 상황 | 함수로 구현 | 클래스로 구현 |
| 상태 저장 | 매번 인자로 넘겨줘야 함 | 내부 속성으로 저장 가능 |
| 기능 조합 | 여러 함수를 나열 | 메서드로 구조화 가능 |
| 설정 관리 | 파라미터가 점점 많아짐 | __init__으로 정리 |
| 확장성 | 새 기능마다 새 함수 | 메서드 추가로 유연하게 확장 가능 |
✅ 클래스 개념 쉽게 이해하기
| 개념 | 정의 | 비유 |
| 클래스 | 상태 + 기능을 함께 정의한 설계도 | 게시판 양식, 붕어빵 틀 |
| 인스턴스 | 클래스로 만든 실제 객체 | 작성된 게시물 한 장, 붕어빵 한 개 |
| 메서드 | 인스턴스가 수행할 수 있는 행동 | 게시글 수정, 삭제 |
| 속성 | 인스턴스가 가진 정보 | 작성자, 내용, 좋아요 수 |
✅ 실무에서는 이렇게 쓰인다
| 분야 | 활용 예시 |
| 데이터 전처리 | TextCleaner, Normalizer 등 클래스로 여러 설정 관리 |
| 모델 학습 | Trainer 클래스에서 학습, 평가, 저장까지 한 번에 |
| 파이프라인 구성 | 각 단계를 PipelineStep 클래스로 구성해 유연한 흐름 설계 |
➡️ 클래스는 복잡한 시스템을 구조화하고, 유지보수와 협업을 쉽게 만든다.
❓ 자주 묻는 질문
| 질문 | 답변 |
| 함수만으로도 가능한데 왜 클래스를 써야 하나요? | 간단한 작업은 함수로 충분하지만, 상태와 동작을 함께 다뤄야 하는 순간이 오면 클래스가 필요하다. |
| 인스턴스를 여러 개 만들면 각각 따로 동작하나요? | ✅ 그렇다. 각 인스턴스는 독립적인 객체이므로 서로 간섭하지 않는다. |
self는 왜 꼭 써야 하나요? | self는 인스턴스 자신을 가리키는 키워드로, 속성이나 메서드에 접근할 때 꼭 필요하다. |
✅ 함수는 동작을 담은 훌륭한 도구
def deposit(balance, amount):
return balance + amount
def withdraw(balance, amount):
return balance - amount
이런 함수는 간단한 입출금 기능을 재사용할 수 있어서 좋다.
하지만…
매번
balance를 넘겨줘야 하고,여러 계좌를 다룰 때는
balance1,balance2, … 복잡해진다 😵
➡️ “상태”를 기억할 수 없다는 한계가 있다.
🧠 비유: 함수 vs 클래스
| 구분 | 비유 | 설명 |
| 함수 | 기능만 담긴 공구 상자 | 설정은 매번 직접 해야 함 |
| 클래스 | 기능 + 상태가 담긴 공구 키트 | 각 도구가 설정 상태를 기억함 |
✅ 생성자와 self 이해하기
class User:
def __init__(self, author, content):
self.author= author
self.content = content
__init__: 인스턴스를 만들 때 자동 실행 (생성자)모든 클래스의 가장 기본이 되는 메소드
인스턴스가 처음 만들어질 때 어떻게 세팅할 것인지 결정
참고: __” ”__로 되어 있는 함수들을 Python에서 중요한 함수
self.name: 인스턴스가 기억하는 속성self: 클래스 내부에서 속성/메소드에 접근할 때User.name: 클래스 밖에서 접근할 때
비유:
self는 현재 조립 중인 제품
사용 방법
my_user = User("elice", "I love coding!")
생성자의 매개변수 vs. 클래스의 속성

from user import User
class Post:
# 생성자는 매개변수로 작성자와 내용을 받아,
# 그 값을 속성에 저장합니다.
def __init__(self, author, content):
# 속성들을 초기화합니다.
self.author = author
self.content = content
self.comments = []
self.likes = 0
# 새로운 사용자 me를 생성합니다.
me = User("Kiwi")
my_post = Post(me, "프로그래밍!")
print(my_post)
속성 만들 때 주의할 점
class Post: # Post를 할 때 생성되는 인스턴스의 틀
def __init__(self, author, content):
self.likes = 0 # 좋아요 개수
self.liked_users = [] # 좋아요를 누른 사람 리스트
my_post = Post("elice", "I love coding!")
my_post.likes += 1
“좋아요” 개수는 올라가지만, “좋아요”를 누른 사람은 추가가 안 됨.
- 이런 모순이 생기면 발견하기 어렵다.
따라서, 클래스를 만들 때 하나의 개념을 뜻하는 속성은 하나여야 한다.
메소드 만들기(개념과 속성 수 일치)
class Post:
def __init__(self, author):
self.author = author
self.liked_users = []
def like(self, user):
self.liked_users.append(user)
def num_likes(self):
return len(self.liked_users)
# 사용 방법
post = Post("Alex")
post.like("user1")
post.like("user2")
post.like("user3")
print(post.num_likes()) # 출력: 3
위처럼 “좋아요“ 개수를 세는 메소드를 만들어서 관리하는 게 좋다.
이렇게 하면, 누가 눌렀는지 자동으로 기록되고, 좋아요 개수는 자동으로
len(self.liked_users)로 셀 수 있다.즉, 메소드를 속성처럼 사용하는 것이며,
이렇게 하면 속성의 값과 num_likes의 결과물이 항상 같게 된다.
💡 참고: @property를 쓰면?
@property
def num_likes(self):
return len(self.liked_users)
이렇게 바꾸면 print할 때, () 없이 속성처럼 쓸 수 있다.
print(post.num_likes) # 출력: 3
원하지 않는 값 배제하기
class Post:
def __init__(self, author):
...
my_post = Post("elice", 1457) # content로 글이 들어와야 하는데 숫자가 들어옴
my_post.like(["Hello","World"]) # user를 하나 받아야 하는데 리스트가 들어옴
- 값이 잘못 들어오면, 배제하고 알려주는 기능이 있어야 한다.
class User:
def __init__(self, year_of_birth):
if type(year_of_birth) is not int:
return # 값이 잘못 들어오면 아무 것도 없이 return
class Post:
def __init__(self, author, content):
if not isinstance(author, User):
return
if type(content) is not str:
return
타입 체크를 해서 잘못 들어오면 아무것도 없이 리턴
타입 비교를 할 때는 “is“와 “is not“을 사용해야 한다.
class User:
def __init__(self, year_of_birth):
if year_of_birth > 2005:
raise Exception("Too young")
- return을 사용하면 코드가 그냥 끝나지만, raise 함수를 사용하면 코드를 끝내면서 사용자에게 그 이유를 알려줄 수 있다.
✅ isinstance() & raise: 견고한 클래스 만들기
1️⃣ isinstance()
def set_age(age):
if not isinstance(age, int):
raise TypeError("나이는 숫자여야 합니다.")
➡️ 입력값이 올바른 타입인지 검사하는 방어코드
2️⃣ raise
def divide(a, b):
if b == 0:
raise ValueError("0으로 나눌 수 없습니다!")
return a / b
➡️ 잘못된 입력에 대해 명확한 에러 메시지와 함께 실행 중단
✨ raise와 사용하는 에러의 종류
raise ValueError("값이 이상해요")
raise TypeError("타입이 틀렸어요")
raise Exception("그냥 일반적인 에러예요")
ValueError: 값이 잘못된 경우TypeError: 타입이 안 맞는 경우Exception: 가장 기본적인 에러 (다른 에러의 부모 클래스)
이런 식으로 다양한 에러 종류 중 하나를 선택해서 raise할 수 있다.
🚧 꼭 기억해야 할 포인트
raise는 에러를 직접 발생시킬 때 쓴다.Exception은 어떤 종류의 에러인지 알려주는 클래스다.직접 에러 메시지를 붙여서 사용자나 개발자에게 원인을 설명할 수 있다.
✅ 실무에서 클래스 + raise + 타입 검사 쓰이는 예
| 상황 | 설명 |
| 데이터 전처리기 | 옵션이 문자열인지 검사 |
| 모델 트레이너 | 하이퍼파라미터 유효성 검사 |
| API 호출 | 필수 파라미터 빠졌는지 확인 후 raise |
def load_file(path):
if not isinstance(path, str):
raise TypeError("파일 경로는 문자열이어야 합니다.")
if not os.path.exists(path):
raise FileNotFoundError(f"파일이 없습니다: {path}")
➡️ 에러는 숨기는 게 아니라, 빨리 드러내야 한다.
🎯 마무리 핵심 정리
| 개념 | 설명 |
| 클래스 | 상태 + 동작을 묶는 설계도 |
| 인스턴스 | 클래스로 만든 실제 객체 |
| self | 현재 객체 자신을 가리키는 예약어 |
| init | 인스턴스를 초기화하는 생성자 |
| isinstance | 타입을 검사하는 함수 |
| raise | 명시적으로 에러를 발생시키는 문법 |
💡 클래스 공부는 객체지향의 출발점
이제 클래스에 익숙해졌다면, 다음은 상속과 다형성을 배우면 된다.
클래스를 통해 프로그램을 더 유연하고 구조화된 방식으로 설계할 수 있게 된다.