[8일차-3] Python

송정근·2026년 5월 29일

매직 메서드와 제너레이터 정리

매직 메서드(magic method) 는 파이썬 특정 문법이나 연산이 실행될 때 자동으로 호출되는 특별한 메서드이다. __len__, __str__, __getitem__ 처럼 이름의 앞뒤에 밑줄 두 개가 붙어 있어 dunder method 라고도 부른다.

매직 메서드를 클래스에 구현하면 len(obj) , print(obj) , obj[0] , obj + other , with obj: 같은 파이썬의 기본 문법과 사용자 정의 객체를 자연스럽게 연결할 수 있다.

1. 매직 메서드란?

매직 메서드는 개발자가 직접 호출하기보다 파이썬 문법에 의해 자동으로 호출되는 메서드이다.

문법호출되는 매직 메서드
print(obj)obj.__str__()
repr(obj)obj.__repr__()
len(obj)obj.__len__()
obj[index]obj.__getitem__(index)
obj[index] = valueobj.__setitem__(index, value)
del obj[index]obj.__delitem__(index)
obj()obj.__call__()
for item in objobj.__iter__(), obj.__next__()
with obj:obj.__enter__(), obj.__exit__()
obj1 + obj2obj1.__add__(obj2)

매직 메서드는 파이썬 문법을 사용자 정의 클래스와 연결하는 약속이다.

2. 객체 출력

객체를 출력할 때 가장 자주 사용하는 매직 메서드는 __str__() 과 __repr__()이다.

2.1 __str__() : 사용자에게 보여줄 문자열

__str__() 은 객체를 사람이 읽기 쉬운 문자열로 표현할 때 사용한다. print(obj)또는 str(obj) 를 호출하면 자동으로 실행된다.

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

    def __str__(self):
        return f"{self.name} {self.quantity}개"


apple = Fruit("사과", 10)

print(apple)
print(str(apple))

출력 결과 :

사과 10개
사과 10개

__str()__ 을 정의하지 않으면 객체를 출력했을 때 기본적으로 클래스 이름과 메모리 주소 형태가 출력된다.

2.2 __repr__() : 개발자를 위한 공식 표현

__repr__() 은 객첼ㄹ 개발자 관점에서 명확하게 표현하는 문자열을 반환한다. repr(obj) 를 호출할 때 실행되며, 가능하다면 객체를 다시 생성할 수 있는 형태의 문자열을 반환하는 것이 권장된다.

class Fruit:
  def __init__(self, name, quantity):
    self.name = name
    self.quantity = quantity
    
    def __str__(self):
      return f"{self.name} {self.quantity}개"
    
    def __repr__(self):
      return f"Fruit'{self.name}', quantity={self.quantity})"
    
apple = Fruit("사과", 10)

print(str(apple))
print(repr(apple))

출력 결과 :

사과 10개
Fruit(name='사과', quantity=10)

일반적으로 __str__() 은 사용자 친화적인 출력, __repr__() 은 디ㅓ깅에 유용한 출력을 담당한다.

3. 반복 가능한 객체와 제너레이터

반복 가능한 객체를 이해하려면 이터러블(iterable), 이터레이터(iterator), 제너레이터(generator)를 구분해야 한다.

개념설명
이터러블iter()를 적용할 수 있는 객체이다. 예: 리스트, 튜플, 문자열
이터레이터iter()와 next()를 모두 구현해 값을 하나씩 꺼내는 객체이다.
제너레이터yield를 사용해 간단히 만드는 이터레이터이다.

3.1 __iter__() 와 __next__() 로 이터레이터 만들기

for 문은 내부적으로 iter() 를 호출해 이터레이터를 얻고, 반복할 때마다 next() 를 호출한다.

class Counter:
    def __init__(self, max_count):
        self.max_count = max_count
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.max_count:
            self.current += 1
            return self.current
        raise StopIteration


counter = Counter(3)

for num in counter:
    print(num)

출력 결과 :

1
2
3

__next__() 는 다음 값을 반환하고, 더 이상 반환할 값이 없으면 StopIteration 예외를 발생시켜 반복을 종료한다.

3.2 제너레이터 함수

제너레이터는 yield 키워드를 사용해 값을 한 번에 하나씩 생성하는 함수이다. 일반 함수처럼 보이지만 yield 를 만다면 값을 반환하고 실행 상태를 유지한다. 다음 호출 시 이전 위치에서 이어서 실행된다.

def counter(max_count):
  current = 0
 	while courrent < max_count:
    current += 1
    yield current
 
for num in counter(3)
	print(num)

출력 결과 :

1
2
3

yield 의 핵심은 다음과 같다.

역할설명
값을 반환한다yield 값의 값을 호출한 쪽에 전달한다.
함수 상태를 저장한다지역 변수와 실행 위치를 기억한다.
이어서 실행한다다음 next() 호출 때 멈춘 위치부터 실행한다.

제너레이터는 자동으로 __iter__()와 __next__()를 제공하므로 직접 이터레이터 클래스를 만들지 않아도 된다.

3.3 제너레이터를 직접 next() 로 호출하기

def counter(max_count):
  current = 0
  while current < max_count:
    current += 1
    yield current
    
gen = counter(2)

print(next(gen))
print(next(gen))

출력 결과 :

1
2

제너레이터는 모든 데이터를 한 번에 만들지 않고 필요할 때 하나씩 생성하므로 메모리 효율이 좋다.

4. 인덱싱과 아이템 조작

사용자 정의 객체도 __getitem__(), __setitem__() , __delitem__() 을 구현하면 리스트나 딕셔너리처럼 사용할 수 있다.

문법호출되는 메서드
obj[index]__getitem__(self, index)
obj[index] = value__setitem__(self, index, value)
del obj[index]__delitem__(self, index)
class MyList:
  def __init__(self):
    self.data = []
    
  def __getitem__(self, index):
    return self.data[index]
  
  def __setitem__(self, index, value):
    self.data[index] = value
  
  def __delitem__(self, index):
    del self.data[index]
    
ml = MyList()
ml.data = [10, 20, 30]

print(ml[0])

ml[1] = 99
print(ml.data)

del m1[0]
print(ml.data)

출력 결과 :

10
[10, 99, 30]
[99, 30]

이렇게 구현하면 MyList 객체가 내부적으로 리스트를 감싸고 있어도 외부에서는 ml[0] 처럼 자연스럽게 사용할 수 있다.

4.1 슬라이싱도 처리할 수 있다.

obj[1:3] 처럼 슬라이싱 하면 __getitem__() 의 index 에 slice 객체가 전달된다.

class MyList:
  def __init__(self, data):
    self.data = data
  
  def __getitem__(self,index):
    return self.data[index]
  
ml = MyList([10, 20, 30, 40])

print(ml[1])
print(ml[1:3])

출력 결과 :

20
[20, 30]

5. 객체를 함수처럼 사용하기

__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

m(10) 은 내부적으로 m.__call__(10)을 호출한다. 객체가 상태를 가지고 있으면서 함수처럼 동작해야 할 때 유용한다.

6. 객체의 길이와 참/거짓 판단

6.1 __len__()

__len__() 은 객체의 길이를 정의한다. len(obj) 를 호출할 때 자동으로 실행된다

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

print(len(basket))

출력 결과 :

3

6.2 __bool__()

__bool__() 은 객체가 조건문에서 참인지 거지인지 판단할 때 호출된다.

class Basket:
  def __init__(self, items):
    self.items = items
    
  def __bool__(self):
    return len(self.items) > 0
  
empty_basket = Basket([])
fruit_basket = Basket(["사괴"])

print(bool(empty_basket))
print(bool(fruit_basket))

출력 결과 :

False
True

7. 연산자 오버로딩

연산자 오버로딩은 +, ==, < 같은 연산자를 사용자 정의 객체에 맞게 동작하도록 만드는 것이다.

7.1 __add__()

__add__() 는 obj1 + obj2 가 실행될 때 호출된다.

class Basket:
  def __init__(self, items):
    self.items = items
  
  def __add__(self, other):
    return Basket(self.items + other.items)
  
  def __repr__(self):
    print f"Basket({self.items})"
  
basket1 = Basket(["사과", "바나나"])
basket2 = Basket(["오렌지"])

result = basket1 + basket2
print(result)

출력 결과 :

Basket(['사과', '바나나', '오렌지'])

7.2 __eq__()

__eq__()는 obj1 == obj2 가 실행될 때 호출된다.

class Fruit: 
  def __init__(self, name, quantity):
    self.name = name
    self.quantity = quantity
    
  def __eq__(self, other):
    return self.name == other.name and self.quantity == other.quantity
  
apple1 = Fruit("사과", 10)
apple2 = Fruit("사과", 10)
apple3 = Fruit("사과", 5)

print(apple1==apple2)
print(apple2==apple3)

출력 결과 :

True
False

비교 대상이 같은 타입인지 확인하려면 다음처럼 방어 코드를 넣을 수 있다.

class Fruit:
  def __init__(self, name, quantity):
    self.name = name
    self.quantity = quantity
    
  def __eq__(self, other):
    if not isinstance(other, Fruit):
      return NotImplemented
    else:
      return self.name==other.name and self.quantity==other.quantity

apple = Fruit("사과", 10)

print(apple == "사과")    

출력 결과 :

False

NotImplemented 는 "이 비교는 내가 처리할 수 없다."는 의미이다. 파이썬은 반대쪽 객체의 비교 메서드를 시도하거나 적잘한 기본 결과를 사용한다.

8. with 문과 컨텍스트 매니저

with 문은 파일, 네티워크 연결, DB 연결처럼 사용 후 반드시 정리해야 하는 자원을 안전하게 관리하기 위한 문법이다.

메서드호출 시점
enter()with 블록에 진입할 때
exit()with 블록이 끝날 때

8.1 기본 컨텍스트 매니저

class MyResource:
  def __init__(self):
    print("자원 열기")
    return self
 
  def __exit__(self, exc_type, exc_value, traceback):
    print("자원 정리")

with MyResource() as resurce:
  print("자원 사용 중")

출력 결과 :

자원 열기 
자원 사용 중
자원 정리

__enter__() 에서 반환한 값은 as 뒤의 변수에 들어간다. __exit__() 은 예외 발생 여부와 관계없이 항상 호출된다.

8.2 예외가 발생해도 __exit__() 은 실행된다.

class MyResource: 
  def __enter__(self):
    print("자원 열기")
    return self
  
  def __exit__(self, exc_type, exc_value, traceback):
    print("자원 정리")
    print("예외 타입", exc_type)
    
    
try:
  with MyResource():
  	print("작업 중")
  	1 / 0
except ZeroDivisionError:
  print("바깥에서 예외 처리")

출력 결과 :

자원 열기
작업 중
자원 정리
예외 타입 : <class 'ZeroDivisionError'>
바깥에서 예외 처리

__exit__()은 예외 정보를 인자로 받는다.

매개변수의미
exc_type발생한 예외 타입
exc_value발생한 예외 객체
traceback예외 발생 위치 정보

8.3 __exit__() 의 반환값

__exit__() 이 True 를 반환하면 예외가 바깥으로 전파되지 않는다. 이를 예외 억제라고 한다.

class IgnoreError:
  def __enter__(self):
    print("시작")
    return self
  
  def __exit__(self, exc_type, exc_value, traceback):
    print("종료")
    return True
  
  
with IgnoreError():
  print("작업 중")
  1 / 0
 
print("프로그램 계속 실행")

출력 결과 :

시작
작업 중
종료
프로그램 계속 실행

예외를 무조건 억제하면 문제를 숨길 수 있으므로, 명확한 의도가 있을때만 True 를 반환해야한다.

9. 마무리

매직 메서드는 파이썬의 문법과 사용자 정의 객체를 연결하는 핵심 기능이다.

목적매직 메서드
객체 문자열 표현__str__(), __repr__()
반복 처리__iter__(), __next__()
인덱싱__getitem__(), __setitem__(), __delitem__()
함수처럼 호출__call__()
길이와 참/거짓__len__(), __bool__()
연산자__add__(), __eq__()
컨텍스트 관리__enter__(), __exit__()

매직 메서드를 잘 활용하면 사용자 정의 객체도 파이썬의 기본 자료형처럼 자연스럽게 사용할 수 있다. 다만 모든 기능을 매직 메서드로 해결하려 하기보다, 파이썬 문법과 자연스럽게 연결될 때 사용하는 것이 좋다.

출처 및 참고 자료

본 정리는 강의 내용과 아래 자료를 참고하여 학습 목적으로 작성하였습니다.

https://ryuzyproject.tistory.com/292

profile
기록하며 성장하는 개발자

0개의 댓글