지난 편에서는 다중 상속 구조에서 발생할 수 있는 다이아몬드 상속 문제와 파이썬이 이를 깔끔하게 해결하는 MRO(메서드 탐색 순서)의 원리를 다루었습니다. 파이썬 클래스를 설계하다 보면 “내가 직접 만든 객체끼리 더하기(+) 연산을 하거나, print()로 출력했을 때 지저분한 메모리 주소 대신 예쁜 문자열이 나오게 만들 수는 없을까?”라는 의문이 들곤 합니다.
기본적으로 파이썬에서 사용자 정의 객체 두 개를 더하려고 obj1 + obj2를 실행하면 TypeError: unsupported operand type(s) 에러가 발생합니다. 파이썬 시스템 입장에서는 개발자가 만든 객체를 어떻게 더해야 할지 알 길이 없기 때문입니다. 하지만 파이썬이 내부적으로 사용하는 특수 메서드들을 클래스 안에 구현해 주면, 내가 만든 객체도 파이썬 기본 데이터 타입처럼 내장 연산자(+, -, ==, <)나 내장 함수(len(), str())와 자연스럽게 어우러져 동작하게 만들 수 있습니다.
저 역시 초보 시절, 객체의 정보를 확인하려고 매번 obj.show_info() 같은 별도의 메서드를 일일이 만들어 부르다가, 파이썬 특유의 매직 메서드를 알고 난 뒤 코드의 가독성이 몇 배는 올라갔던 경험이 있습니다. 오늘은 언더바 2개로 시작하고 끝나는 매직 메서드(Dunder Methods)의 핵심 종류와 실전 활용법을 정리해 드립니다.
1. 매직 메서드(Dunder Methods)란 무엇인가?
'매직 메서드'는 메서드 이름 앞뒤로 언더바 2개(__)가 붙어있는 파이썬의 약속된 특수 메서드들입니다. Double Underbar를 줄여 '던더(Dunder) 메서드'라고 부르기도 합니다.
우리가 이미 사용했던 생성자 __init__ 역시 매직 메서드의 일종입니다. 던더 메서드의 가장 큰 특징은 개발자가 직접 obj.__add__()처럼 호출하기보다는, 파이썬의 연산자나 내장 함수를 사용할 때 파이썬 시스템이 뒤에서 자동으로 해당 메서드를 불러온다는 점입니다.
예를 들어 우리가 1 + 2를 계산할 때, 파이썬 내부에서는 정수 객체 1에 정의된 1.__add__(2) 매직 메서드가 작동하는 방식입니다.
2. 객체를 예쁘게 표현하는 문자열 매직 메서드: __str__과 __repr__
클래스로 객체를 만든 뒤 print(user1)을 실행해보면 <__main__.Member object at 0x0000021A...>처럼 사람이 읽기 힘든 메모리 주소만 나옵니다. 이때 __str__이나 __repr__을 구현하면 출력 형태를 깔끔하게 다듬을 수 있습니다.
__str__: 일반 사용자에게 보여줄 보기 편하고 친절한 문자열을 리턴합니다. (print()함수나str()호출 시 자동 실행)__repr__: 개발자가 디버깅할 때 사용할 명확하고 공식적인 객체 상태 문자열을 리턴합니다. (파이썬 대화형 콘솔이나 리스트 내부 객체 출력 시 자동 실행)
class Money:
def __init__(self, amount, currency="KRW"):
self.amount = amount
self.currency = currency
# print() 호출 시 가독성 좋은 문자열 출력
def __str__(self):
return f"{self.amount:,} {self.currency}"
# 디버깅 시 객체 재창조가 가능한 상태 표현
def __repr__(self):
return f"Money({self.amount}, '{self.currency}')"
m1 = Money(15000)
print(m1) # __str__ 작동 -> 출력: 15,000 KRW
print(repr(m1)) # __repr__ 작동 -> 출력: Money(15000, 'KRW')
3. 산술 연산자 덧씌우기: __add__와 __sub__
이제 내가 만든 객체들끼리 + 연산이나 - 연산이 가능하도록 매직 메서드를 오버라이딩해 보겠습니다.
class Money:
def __init__(self, amount):
self.amount = amount
def __str__(self):
return f"{self.amount:,}원"
# + 연산자 행동 정의 (self + other)
def __add__(self, other):
# 상대방도 Money 객체인 경우
if isinstance(other, Money):
return Money(self.amount + other.amount)
# 상대방이 단순 정수인 경우
elif isinstance(other, int):
return Money(self.amount + other)
return NotImplemented
m1 = Money(10000)
m2 = Money(20000)
# 객체끼리의 덧셈 연산 실행!
m3 = m1 + m2
print(m3) # 출력: 30,000원
# 객체와 정수의 덧셈 연산 실행!
m4 = m1 + 5000
print(m4) # 출력: 15,000원
m1 + m2를 실행하면 파이썬은 자동으로 m1.__add__(m2)를 수행합니다. 이처럼 매직 메서드를 구현해두면 외부에서 복잡한 메서드 이름(m1.add_money(m2))을 외울 필요 없이 자연스러운 기호 연산이 가능해집니다.
4. 비교 연산자와 길이 판별: __eq__와 __len__
두 객체의 속성값이 같은지 비교하는 == 연산자나, 객체 내부의 요소 개수를 세는 len() 함수 역시 매직 메서드로 직접 제어할 수 있습니다.
__eq__:==연산자 실행 시 호출됩니다. 기본적으로 파이썬은 두 객체의 메모리 주소가 같은지 비교하지만,__eq__를 오버라이딩하면 "속성값이 같으면 같은 객체로 인정"하도록 변경할 수 있습니다.__len__:len(obj)실행 시 호출되며, 반드시 정수(int) 값을 반환해야 합니다.
class Cart:
def __init__(self, items):
self.items = items
# len(cart) 사용 가능
def __len__(self):
return len(self.items)
# cart1 == cart2 사용 가능 (내부 데이터 비교)
def __eq__(self, other):
if isinstance(other, Cart):
return self.items == other.items
return False
cart1 = Cart(["사과", "바나나"])
cart2 = Cart(["사과", "바나나"])
print(len(cart1)) # __len__ 작동 -> 출력: 2
print(cart1 == cart2) # __eq__ 작동 -> 출력: True (메모리 주소는 다르지만 내용물이 같음)
5. 매직 메서드 작성 시 주의할 점: 과유불급
매직 메서드는 코드를 직관적이고 파이썬답게(Pythonic) 만들어주지만, 남용하면 오히려 독이 될 수 있습니다.
예를 들어 Member 객체 두 개를 더하는데 + 연산자가 갑자기 회원의 이름을 이어붙이거나 포인트를 더하는 등 직관적이지 않은 동작을 하도록 만들면, 코드를 읽는 다른 동료 개발자에게 커다란 혼란을 주게 됩니다.
따라서 매직 메서드는 상식적인 연산자 개념(금액의 덧셈, 좌표의 계산, 리스트 형태의 데이터 다루기 등)에 들어맞는 경우에만 절제하여 사용하는 것이 좋은 클래스 설계의 핵심입니다.
핵심요약
매직 메서드(Dunder Methods)는 언더바 2개(
__)로 둘러싸인 특수 메서드로, 파이썬의 내장 연산자 및 함수와 객체를 연동시켜 줍니다.__str__과__repr__을 구현하면 객체를 사람이 읽기 편한 깔끔한 문자열 포맷으로 출력하여 디버깅 편의성을 극대화할 수 있습니다.__add__,__eq__,__len__등을 통해 산술 연산, 값 비교, 길이 판별 행동을 객체에 부여할 수 있으나, 직관성을 해치지 않도록 적절히 사용하는 것이 중요합니다.


0 댓글