Skip to main content

Command Palette

Search for a command to run...

[Python TIL] 함수(Function)와 클래스(Class)

Updated
•8 min read•View as Markdown
K

I'm currently learning Python and studying RAG (Retrieval-Augmented Generation).

함수 → 클래스로 가는 사고 전환

함수는 "동작"만 담당했지만,

어떤 대상(데이터)과 관련된 동작이 많아지면

"이 데이터와 함수들을 한 덩어리로 묶는 것이 좋다!"

→ 이게 바로 클래스(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명시적으로 에러를 발생시키는 문법

💡 클래스 공부는 객체지향의 출발점

이제 클래스에 익숙해졌다면, 다음은 상속과 다형성을 배우면 된다.
클래스를 통해 프로그램을 더 유연하고 구조화된 방식으로 설계할 수 있게 된다.

Python TIL

Part 6 of 26

알고리즘 풀이 중 마주친 Python 문법들을 기록합니다. 복잡한 개념도 쉽게 이해할 수 있도록 정리했어요. 필요할 때 빠르게 참고할 수 있는 문법 노트가 되었으면 좋겠습니다! 😊

Up next

[Python TIL] 🐍 Python 기초: 전역변수, 지역변수부터 모듈, 패키지, 웹 요청까지!

전역 변수와 지역 변수, 함수와 메서드, 인자와 매개변수, 모듈과 패키지

More from this blog

[Python 자료구조] Binary Tree(이진 트리) 개념과 배열

자료구조 중에서도 가장 자주 등장하고, 가장 중요한 트리 중 하나가 바로 이진트리(Binary Tree)이다.만약 자료구조 공부를 처음 시작했다면, 이진트리는 꼭 제대로 이해하고 넘어가야 할 핵심 개념이다. 그렇다면 이진트리는 도대체 어떤 구조이며, 왜 이렇게 중요할까? 🌳 이진트리란? 이진트리(Binary Tree)는 이름 그대로 자식 노드를 최대 두 개까지만 가질 수 있는 트리를 의미한다.자식 노드가 1개거나 0개일 수도 있지만, 최대...

Apr 8, 20253 min read

[Python 자료구조] Tree(트리) 구조

회사 조직도, 가계도, 또는 어떤 계층적인 구조를 표현할 때 자주 등장하는 그림이 있다. 바로 '트리 구조'이다. 그런데 이 구조를 처음 보면, "도대체 어디가 나무야?" 라고 생각할 수 있다. 하지만 나무를 거꾸로(180도 돌려서) 생각해보면 이해가 쉽다. 위쪽에 커다란 줄기(기둥)가 있고, 아래로 가지가 퍼져 나가는 모습과 아주 흡사하기 때문이다. 그래서 우리는 이런 구조를 트리(Tree) 구조라고 부른다. 🧩 트리(Tree)란 무엇인가...

Apr 8, 20253 min read

[Python 자료구조] Stack(스택)

스택 정의, 값 넣기/빼기 프로그래밍을 처음 접할 때 나오는 대표적인 자료구조 중 하나가 바로 스택(Stack) 이다. 이름만 들으면 어렵게 느껴질 수 있지만, 실제로는 우리 일상에서도 쉽게 찾아볼 수 있는 개념이다. 예를 들어 젠가(Jenga) 게임을 떠올려보자. 스택이란? 젠가로 이해하는 Stack의 원리 젠가 블럭을 하나씩 통에 넣고 꺼내는 상황을 상상해보자. 가장 최근에 넣은 블럭은 통의 가장 위에 쌓이게 된다. 그리고 꺼내고자 할 때...

Apr 7, 20253 min read

[Sql] 제약 조건과 무결성

SQL을 공부하다 보면 반드시 만나게 되는 용어들이 있다. 바로 기본키, 외래키, 무결성 제약 조건 같은 개념이다. 이 글에서는 이 용어들을 단순히 암기하는 게 아니라, 예시를 통해 자연스럽게 이해할 수 있도록 정리해보았다. 🎯 제약 조건이란? 제약 조건(Constraint)은 데이터베이스에 저장되는 데이터의 정확성과 신뢰성을 보장하기 위해 설정하는 규칙이다. ✅ 한 줄에 하나씩만 작성해야 하며, 테이블 생성 시 컬럼 옆에 정의하거나 AL...

Apr 7, 20253 min read

[Python TIL] Python에서 False와 True로 평가되는 값들

파이썬에서는 if 조건문이나 while 같은 컨트롤 흐름에서 자동으로 False로 간주되는 값들이 있다. 이걸 "Falsy 값" 또는 "Falsy Object" 라고 부른다. 이걸 알아두면 코드를 훨씬 깔끔하게 쓸 수 있다. 예를 들어, if not my_list: 같은 표현이 빈 리스트를 체크하는 데 쓰이기도 하기 때문이다. ⚠️ 파이썬에서 False로 평가되는 값들 (Falsy 값) 유형예시설명 숫자형0, 0.0, 0j정수, ...

Apr 6, 20252 min read

Code Compass

75 posts