Python/기초

파이썬 매직 메서드와 제너레이터

sdesign416 2026. 6. 1. 15:11

매직 메서드(Magic Method)는 스페셜 메서드라고도 불리며,

메서드 양쪽에 언더스코어(__) 두개로 감싼 형태를 가지고 있다.

 

매직 메서드는 클래스 내부에서 특별한 기능을 수행할 수 있으며,

Python의 내장 기능과 상호작용할 때 호출된다.

 

주요 매직 메서드

  • __str__()
  • __repr__()
  • __getitem__()
  • __setiten__()
  • __delitem__()
  • __call__()
  • __len__()
  • __wnter__()
  • __exit__()

1. 객체를 출력하는 매직 메서드

1_1) __str__()

__str__()는 객체를 사람이 읽기 쉬운 문자열로 표현하기 위해 사용되는 매직 메서드이다.

보통 print나 srt로 객체가 호출될 때 자동으로 실행되며

이 메서드를 클래스에 정의하면

객체를 출력할 때 기본 주소값 대신 원하는 형태로 내용을 보여줄 수 있다.

 

👎 매직메서드 사용 전

class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

apple = Fruit("사과", 10)
print(apple) # 객체의 주소가 나타남(기본기능)

실행결과 ▶

 

👍 매직메서드 사용 후

class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity
    # object 클래스의 기본 __str__ 메서드를 오버라이딩
    # : 주소값 대신 커스텀 문자열 반환
    def __str__(self): # str()함수와 연결된 매직메서드
        return f"{self.name} {self.quantity}개"

apple = Fruit("사과", 10)
print(apple)        # __str__ 호출
print(apple)        # str() 생략된것
print(str(apple))   # __str__ 호출

실행결과 ▶


1_2) __repr__()

__repr__()은 객체를 개발자 관점에서 정확하게 표현하는 문자열을 반환해주는 기능을 가지고있다.

가능하다면 객체를 다시 생성할 수 있는 형태의 문자열로 반환하는것을 권장하며,

__str__()은 사람이 읽기 좋은 출력인 반면에,

__repr__()은 객체의 구조와 상태를 명확하게 보여주는 공식적인 표현이다.

 

💡 __repr__()을 사용하는 이유

출력된 글자를 그대로 복사하여 코드창에 붙여넣기하면

똑같은 객체를 바로 다시 쉽게 만들 수 있기 때문이다.

ex ) apple = Fruit(name='사과', quantity=10)

위 예시는 apple변수에 객체를 생성하여 넣는것이다.

 

👍 매직메서드 사용

결과가 객체를 만드는 형식의 코드로 출력이 된다.  (Fruit(name='사과', quantity=10))

class Fruit:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity

    # 객체의 명확한 상태를 표현하는 매직 메서드
    # 결과값을 그대로 복사해 객체를 다시 생성할 수 있는 형태로 작성하는 것 권장
    def __repr__(self):
        return f"Fruit(name='{self.name}', quantity={self.quantity})"

apple = Fruit("사과", 10)
print(repr(apple))  # __repr__ 호출
print(apple)        # __str__ 없으면 __repr__ 호출
# __str__을 함께 사용하면 __str__이 먼저 출력됨

실행결과 ▶


2. 제너레이터

제너레이터(generator)는 yield 키워드를 사용하여 

값을 한 번에 하나씩 생성하여 반환해주는 반복 객체이다.

 

함수 형태로 사용하며 간단하게 이터레이터를 만들 수 있는 파이썬의 기능이다.

 

일반함수처럼 보이긴 하지만 yield를 만나는 순간

값을 반환하면서 실행 상태를 유지하고

다음 호출을 진행할 때 이어서 실행이 되기 때문에 

전체 데이터를 한번에 만들지 않아 메모리 효율이 매우 좋다. (필요할때마다 생성하여 사용)

※ range() 함수를 만들 때 제너레이터 방식이 사용되었다.


2_1) 매직메서드와 이터레이터

for문이 내부적으로 나동으로 iter() 함수를 호출한다.

이터레이터는 이터러블로부터 실제로 값을 하나씩 꺼내오는 객체로

__iter__()와 __next__() 메서드를 모두 구현하고 있다.

  • __iter__() : 반복을 시작함
  • __next__() 값을 하나씩 꺼내고 더이상 꺼낼 값이 없으면 StopIteration을 발생시켜 반복 종료

 

📌  for문 내부 동작 원리

  1. counter(이터러블 객체)를 iter()함수에 집어넣어 이터레이터를 만든다.
  2. 반복하면서 next() 함수로 자동 호출하여 값을 하나씩 꺼내 num 담는다.
  3. 더 이상 꺼낼 값이 없으면 for문을 스스로 종료한다. (StopIteration)
class Counter:
    def __init__(self, max):
        self.max = max
        self.current = 0

    def __iter__(self):
        return self  # 자기 자신을 iterator로 사용

    def __next__(self):
        if self.current < self.max:
            self.current += 1
            return self.current
        else:
            raise StopIteration
        
counter = Counter(3)

for num in counter:
    print(num)

실행결과 ▶


2_2) 제너레이터 사용 방법

yield는 값을 반환하는 return의 형식을 띄고있으며,

함수 형태를 저장하고 다음 호출을 진행 할 때 이어서 실행할 수 있다.

 

제너레이터는 자동으로 __iter__ + __next__를 만들어주고있기때문에

즉, yield는 iter()와 next()를 동시에 사용할 수 있도록 만들어주는 키워드이다.

def counter(max):
    current = 0
    while current < max:
        current += 1
        yield current # current깂을 기억해줌(yield의 역할) = return의 의미

for num in counter(3):
    print(num)

실행결과 ▶


3. 인덱싱 매직 메서드

많은 양의 데이터를 관리할 때 주로 Class로 관리를 하게 되는데

이때 인덱싱 매직메서드는 필수적으로 사용된다.

 

실제로 데이터분석, 머신러닝, 딥러닝에서 매우 중요한 매직메서드이다.


3_1) __getitem__()

__getitem__()은 객체에서 인덱스나 키를 이용해 값을 조회할 때 호출되는 매직 메서드이다.

이 메서드를 사용하면 사용자 정의 객체도 리스트나 딕셔너리처럼 데이터를 꺼낼 수 있고,

내부적으로는 전달된 인덱스나 키를 기반으로 값을 반환하도록 작성한다.

👎 매직메서드 사용 전 : TypeError 발생

# 인덱싱이 안됨
class MyList:
    def __init__(self):
        self.data = []

ml = MyList() # 객체 자체가 리스트가 아닌 안에 속성이 리스트임
ml.data = [10, 20, 30]

print(ml[0]) # TypeError → 객체 자체가 리스트가 아니기때문!

실행결과 ▶

 

👍 매직메서드 사용 후

  1. ml[0] → __getitem__의 index에 0이 전달됨
  2. return self.data[index] : 어느녀석에 인덱싱할거야?
  3. self.data = [] 여기로 가서 0번 인덱스 찾아서 출력
class MyList:
    def __init__(self):
        self.data = []

    def __getitem__(self, index):
        # 대괄호([])를 쓸 때 자동 호출됨. 
        # 어떤 리스트에서 값을 꺼낼지 정하는 역할!
        return self.data[index]

ml = MyList()
ml.data = [10, 20, 30] # 빈 상자였던 self.data에 실제 데이터를 채워 넣음

print(ml[0])

실행결과 ▶


3_2) __setitem__()

__setitem__()은 객체에서 인덱스나 키를 이용하여 값을 할당해줄 때 호출되는 매직메서드이다.

obj[index] = value 형태의 코드가 실행되면 자동으로 호출되며

이 메서드를 구현하여 사용자 정의 객체에 값을 저장하거나 수정하는 동작을 할 수 있다.

즉, 전달된 인덱스와 값을 이용해서 내부 데이터를 업데이트하도록 구현하는 역할을 한다.

class MyList:
    def __init__(self):
        self.data = []

    def __setitem__(self, index, value):
        self.data[index] = value

ml = MyList()
ml.data = [10, 20, 30]
              
ml[1] = 99     # __setitem__
print(ml.data) # [10, 99, 30]

실행결과 ▶


3_3) __delitem__()

__delitem__()은 객체에서 인덱스나 키를 이용하여 값을 삭제할 때 호출되는 매직 메서드이다.

del obj[index] 형태의 코드로 실행하여 자동 호출이 가능하며

이 메서드를 사용하여 사용자 정의 객체에서 특정 데이터를 삭제하는 로직을 정의할 수 있다.

작성할땐 전달된 인덱스를 기반으로 내부 데이터를 제거하도록 작성해야한다.

class MyList:
    def __init__(self):
        self.data = []

    def __delitem__(self, index):
        del self.data[index]

ml = MyList()
ml.data = [10, 20, 30]

del ml[0]      # __delitem__
print(ml.data) # [20, 30]

실행결과 ▶


4. 객체를 함수나 값처럼 사용하는 매직 메서드

4_1) __call__()

__call__()은 객체를 함수처럼 호출할 수 있도록 만들어주는 매직 메서드이다.

클래스 인스턴스가 일반 함수처럼 동작할 수 있으며,

내부 상태를 활용한 연산이나 처리 로직을 간결하게 표현할 수 있다.

 

아래 코드 예제는 만들어진 객체를 함수처럼 사용하면 10이라는 숫자가 __call__에 들어가 실행된다.
즉, 그냥 함수처럼 만든 객체를 사용할 수 있다는 것이다!

class Multiplier:
    # 생성자
    def __init__(self, n):
        self.n = n

    # 매직메소드
    def __call__(self, x):
        return x * self.n
        
# 객체생성
m = Multiplier(3)
# 출력
print(m(10))  # 30

실행결과 ▶


4_2) __len__()

__len__()은 객체의 길이를 정의하는 매직 메서드이다.

해당 메서드는 len과 같은 기능을 가지고 있다.

사용자 정의 객체도 리스트나 문자열처럼 길이를 가질 수 있으며,

내부 데이터의 개수를 반환하도록 작성한다.

 

👎 매직메서드 사용 전 : TypeError 발생

class Basket:
    def __init__(self):
        self.items = []

b = Basket()
b.items = ["사과", "바나나", "오렌지"]

# 객체자체는 갯수를 셀 수 없음
print(len(b))

실행결과 ▶

 

👍 매직메서드 사용 후

class Basket:
    def __init__(self):
        self.items = []

    def __len__(self):
        return len(self.items)
        
b = Basket()
b.items = ["사과", "바나나", "오렌지"]

print(len(b))  # 3

실행결과 ▶


5. with문과 함께 사용하는 매직 메서드

with문은 파일, 네트워크, DB연결처럼 사용한 후에 반드시 정리(clean-up)가 필요한 자원을 안전하게 관리하기 위한 문법이다.

블록이 시작될 때 필요한 작업을 수행하고 종료될 때 자동으로 정리 작업을 실행하며,

이를 통해 try-finally를 직접 작성하지 않아도 되고,

예외가 발생하더라도 자원지 정상적으로 해제되도록 보장할 수 있다.

 

즉, 우리가 알아서 열고닫고할땐 안쓰더라도 큰 상관은 없지만

with문을 사용하면 굳이 닫는 행위를 하지 않아도 되기 때문에 깔끔하게 관리할 수 있다.

그리고 에러가 나더라도 자원이 알아서 삭제되도록 도와주는 기능도 가지고있다.

 

  • 내가 하는 것 : 파일 열기, 수정(준비와 작업)
  • with문이 알아서 하는 것 : 열기 도우미(enter), 자동 닫기(exit)

5_1) __enter__()

__enter__()는 with문에 진입할 때 자동으로 호출되는 매직 메서드이다.

자원을 준비하거나 초기화하는 역할을 하며

해당 메서드에서 반환한 값은 as(별칭)뒤의 변수에 할당되러 with블록 내부에서 사용할 수 있다.


5_2) __exit__()

__exit__()는 with블록이 끝날 때 자동으로 호출되는 매직 메서드이다.

사용한 자원을 정리(clean-up)하는 역할을 하며,

해당 메서드는 예외 발생 여부와 관계없이 반드시 실행되게 된다.

만약 예외가 발생할 경우 예외 타입, 값, traceback 정보를 인자로 받아 처리할 수 있다.

 

👍 기본 사용

class MyResource:
    # width문 시작 시 자동 실행 메서드
    def __enter__(self):
        print("자원 열기")
        return self # 리턴된 자원(self)이 as뒤 res 변수로 들어감

    # width문 블록 빠져나갈 때 자동실행 메서드(자원정리)
    # 코드가 정상종료, 중간에러가 나더라더 무조건 실행
    def __exit__(self, exc_type, exc_value, traceback):
        print("자원 정리")

# width문이 시작되면 __enter__ 실행
with MyResource() as res:
    print("자원 사용 중")  # 내가 하고싶은 작업 

# 이 자리를 벗어나는 순간 내부적으로 __exit__가 실행되어 자동으로 뒷정리가 됨

실행결과 ▶


👍 에러가 발생하더라도 exit()가 실행되는 예제

print 작업중까지만 작성하고 종료되는게 아닌(에러로 인해)
with문을 다 실행하고(필요한것들 저장하고) 끝낸다.

[핵심 원리 주석]
에러가 발생해서 원래라면 프로그램이 즉시 멈춰야 하지만,
with문 덕분에 튕겨 나가기 직전 __exit__ 구문을 '무조건' 들러서 자원 정리를 마친 뒤 종료된다.
(덕분에 에러가 나도 열려있던 파일이나 자원이 안전하게 닫힘!)

class MyResource:
    def __enter__(self):
        print("자원 열기")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("자원 정리")
        print("예외 타입:", exc_type)

with MyResource():
    print("작업 중")
    1 / 0  # 에러 발생

실행결과 ▶ 모든 작업을 마치고 에러가 나옴