__repr__, __eq__, __add__, __len__, __iter__ 등 핵심 특수 메서드를 구현해 파이썬 객체 프로토콜을 완전히 이해한다.
🎯 이 강의에서 배우는 것
파이썬의 특수 메서드(Special Methods, 또는 "dunder methods" — double underscore)는 파이썬 언어 자체가 호출하는 메서드입니다. len(obj)를 호출하면 파이썬이 내부적으로 obj.__len__()을 실행합니다. 이 메서드들을 구현하면 내장 함수, 연산자, for 루프, with 문이 내 클래스와 자연스럽게 작동합니다. 파이썬의 데이터 모델 전체가 이 특수 메서드 위에 세워져 있습니다.
🖨️ 객체 표현 — __repr__과 __str__
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self) -> str:
# 개발자용: eval(repr(obj)) == obj 가 되도록 작성하는 것이 이상
return f"Point({self.x!r}, {self.y!r})"
def __str__(self) -> str:
# 사용자용: 보기 좋은 형태
return f"({self.x}, {self.y})"
def __format__(self, format_spec: str) -> str:
# f-string: f"{p:.2f}" 와 같은 포맷 스펙 처리
if format_spec:
return f"({self.x:{format_spec}}, {self.y:{format_spec}})"
return str(self)
p = Point(1.5, 2.7)
print(repr(p)) # Point(1.5, 2.7) — __repr__
print(str(p)) # (1.5, 2.7) — __str__
print(p) # (1.5, 2.7) — print()는 __str__ 우선
print(f"{p}") # (1.5, 2.7)
print(f"{p:.2f}") # (1.50, 2.70) — __format__
# 리스트 안에서는 __repr__ 사용
points = [Point(1, 2), Point(3, 4)]
print(points) # [Point(1, 2), Point(3, 4)]
# __repr__만 구현하면 __str__도 __repr__로 대체됨
# — 반드시 __repr__을 먼저 구현하고, 필요 시 __str__ 추가
⚖️ 비교 연산자 — __eq__, __lt__, __hash__
from functools import total_ordering
@total_ordering # __eq__와 하나의 비교 메서드만 구현하면 나머지 자동 생성
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
def __repr__(self):
return f"Temperature({self.celsius}°C)"
def __eq__(self, other) -> bool:
if not isinstance(other, Temperature):
return NotImplemented # 파이썬이 반대 측 시도
return self.celsius == other.celsius
def __lt__(self, other) -> bool:
if not isinstance(other, Temperature):
return NotImplemented
return self.celsius < other.celsius
def __hash__(self) -> int:
# __eq__를 구현하면 __hash__는 None이 됨 (set/dict 키 불가)
# 해시 가능하게 만들려면 명시적으로 구현 필수
return hash(self.celsius)
t1 = Temperature(20)
t2 = Temperature(30)
t3 = Temperature(20)
print(t1 == t3) # True
print(t1 < t2) # True
print(t1 > t2) # False — @total_ordering이 자동 생성
print(t1 <= t3) # True — 자동 생성
print(sorted([t2, t1, t3])) # 정렬 가능!
# set과 dict 키로도 사용 가능 (__hash__ 덕분)
temp_set = {t1, t2, t3}
print(len(temp_set)) # 2 (t1과 t3는 같은 해시 + 같은 값)
➕ 산술 연산자 — __add__, __mul__, __radd__
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other: "Vector") -> "Vector":
# v1 + v2 → v1.__add__(v2)
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
# v * 3 → v.__mul__(3)
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector":
# 3 * v → v.__rmul__(3) (반사 메서드)
# 파이썬: 3.__mul__(v) 가 NotImplemented면 v.__rmul__(3) 시도
return self.__mul__(scalar)
def __iadd__(self, other: "Vector") -> "Vector":
# v += other (인-플레이스, 가능하면 self 수정 후 반환)
self.x += other.x
self.y += other.y
return self
def __neg__(self) -> "Vector":
# -v (단항 음수)
return Vector(-self.x, -self.y)
def __abs__(self) -> float:
# abs(v) → 벡터의 크기
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6) — __rmul__
print(-v1) # Vector(-1, -2)
print(abs(v2)) # 5.0
v1 += v2
print(v1) # Vector(4, 6) — __iadd__
📦 컨테이너와 이터레이터 프로토콜
class Playlist:
def __init__(self, name: str):
self.name = name
self._songs = []
def add(self, song: str) -> None:
self._songs.append(song)
# 컨테이너 프로토콜
def __len__(self) -> int:
return len(self._songs)
def __getitem__(self, index):
return self._songs[index] # 슬라이싱도 자동 지원!
def __setitem__(self, index, value):
self._songs[index] = value
def __delitem__(self, index):
del self._songs[index]
def __contains__(self, song: str) -> bool:
return song in self._songs # in 연산자
def __repr__(self):
return f"Playlist({self.name!r}, {len(self)} songs)"
pl = Playlist("내 플레이리스트")
pl.add("보헤미안 랩소디")
pl.add("Yesterday")
pl.add("Hotel California")
print(len(pl)) # 3 — __len__
print(pl[0]) # 보헤미안 랩소디 — __getitem__
print(pl[1:]) # 슬라이싱 자동 지원
print("Yesterday" in pl) # True — __contains__
# __getitem__이 있으면 for 루프도 자동 지원!
for song in pl:
print(f" ♪ {song}")
# 커스텀 이터레이터 클래스
class CountUp:
def __init__(self, start: int, stop: int):
self.current = start
self.stop = stop
def __iter__(self):
return self # 자기 자신이 이터레이터
def __next__(self) -> int:
if self.current >= self.stop:
raise StopIteration # for 루프 종료 신호
value = self.current
self.current += 1
return value
for n in CountUp(1, 6):
print(n, end=" ") # 1 2 3 4 5
⚠️ 자주 하는 실수
- __eq__ 구현 후 __hash__ 빠뜨리기: 파이썬에서
__eq__를 구현하면 자동으로__hash__ = None이 됩니다. 객체를set이나dict키로 쓰려면 반드시__hash__도 구현하세요. 두 메서드는 일관성이 있어야 합니다:a == b → hash(a) == hash(b). - __repr__에서 eval 불가능한 문자열 반환:
__repr__의 이상적 목표는eval(repr(obj)) == obj입니다. 꼭 지킬 필요는 없지만, 최소한 클래스 이름과 핵심 속성이 포함되어 디버깅에 유용해야 합니다. - 비교 메서드에서 NotImplemented 반환 안 하기: 알 수 없는 타입과 비교할 때
raise TypeError가 아닌return NotImplemented를 반환해야 파이썬이 반대 측의 반사 메서드를 시도합니다.
📝 정리 및 다음 강의 예고
- 특수 메서드: 파이썬이 직접 호출하는
__name__형태. 내장 함수·연산자와 연결. __repr__: 개발자용.__str__: 사용자용.__repr__을 먼저,__str__은 필요 시.__eq__구현 시__hash__도 함께.@total_ordering으로 비교 메서드 자동 완성.- 컨테이너:
__len__+__getitem__만으로len(), 인덱싱, 슬라이싱,for루프 모두 가능.
다음 강의: 프로퍼티와 캡슐화 —
@property로 getter/setter를 파이썬답게 구현하고,__getattr__/__setattr__로 속성 접근을 제어합니다.
관련 주제
- __repr__·__str__
- 비교 연산자 특수메서드
- __add__·__radd__
- 컨테이너 프로토콜(__getitem__)
- 이터레이터 프로토콜(__iter__)
- __enter__·__exit__
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 파이썬 기초 40강 — 처음 배우는 프로그래밍
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
파이썬 기초 40강 — 처음 배우는 프로그래밍
이 강의가 속한 시리즈는 총 40강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
0/1000
불러오는 중...
