매직 메서드(Special Methods) 개요
Python의 __dunder__ 매직 메서드 전체 카테고리, 연산자 오버로딩 원리, Vec2 예제로 보는 산술·비교·표현 메서드 구현 방법을 설명합니다.
지난 글에서 __new__와 __init__이 객체 생성의 두 단계를 담당한다는 것을 보았습니다. 이처럼 이름 앞뒤로 밑줄 두 개(__)가 붙은 메서드를 매직 메서드(magic method) 또는 특수 메서드(special method), **던더 메서드(dunder method)**라고 부릅니다. Python이 특정 연산을 수행할 때 자동으로 호출하는 메서드들입니다.
매직 메서드란
a + b를 평가할 때 Python은 내부적으로 a.__add__(b)를 호출합니다. len(obj)는 obj.__len__()을 호출합니다. 이처럼 연산자, 내장 함수, 문장이 클래스의 특정 메서드와 연결됩니다. 이 메서드를 정의하면 사용자 정의 클래스가 내장 타입처럼 동작합니다.
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx]
ml = MyList([10, 20, 30])
print(len(ml)) # 3
print(ml[1]) # 20
for x in ml: # __iter__ 없어도 __getitem__으로 순회 가능
print(x)
카테고리 개요
표현 메서드: __repr__과 str
__repr__은 개발자용 문자열, __str__은 사용자용 문자열을 정의합니다. __str__이 없으면 str()도 __repr__을 사용합니다.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(1, 2)
print(repr(p)) # Point(1, 2)
print(str(p)) # (1, 2)
print(p) # (1, 2) — print는 str() 사용
call: 호출 가능 객체
__call__을 정의하면 인스턴스를 함수처럼 호출할 수 있습니다.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10
print(double(10)) # 20
print(callable(double)) # True
이 패턴은 상태를 가진 함수가 필요할 때 클로저의 대안으로 활용됩니다.
bool: 불리언 변환
if obj: 평가 시 Python은 obj.__bool__()을 호출합니다. 없으면 __len__()이 0인지 확인합니다. 둘 다 없으면 항상 True입니다.
class Account:
def __init__(self, balance):
self.balance = balance
def __bool__(self):
return self.balance > 0
acc = Account(100)
if acc:
print("잔고 있음") # 출력됨
empty = Account(0)
if not empty:
print("잔고 없음") # 출력됨
Vec2 예제: 여러 매직 메서드 함께
반사 메서드 (reflected methods)
a + b에서 a.__add__(b)가 NotImplemented를 반환하면, Python은 b.__radd__(a)를 시도합니다. 서로 다른 타입 간 연산을 지원할 때 유용합니다.
class Vec2:
def __init__(self, x, y):
self.x, self.y = x, y
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vec2(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar): # 2 * v 지원
return self.__mul__(scalar)
v = Vec2(1, 2)
print(v * 3) # Vec2(3, 6) — __mul__
print(2 * v) # Vec2(2, 4) — __rmul__
주의사항
매직 메서드는 직접 호출하지 않고 연산자나 내장 함수를 통해 사용하는 것이 원칙입니다. a.__add__(b) 대신 a + b를 씁니다. 매직 메서드 이름을 임의로 만들어서는 안 됩니다(__mymethod__처럼 쓰지 말 것). Python이 이미 정의하지 않은 던더 이름은 미래에 언어가 예약할 수 있습니다.
지난 글: init vs new: 객체 생성의 두 단계
다음 글: repr vs str
읽어주셔서 감사합니다. 😊