모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

35강 / 전체 40강

특수 메서드(dunder) — 파이썬 객체 프로토콜

11분 읽기 조회 0

__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

불러오는 중...