본문 바로가기
Python

28. 매직 메서드 - 파이썬 객체의 비밀

by 샤나엘 2026. 2. 23.
반응형

매직 메서드 - 파이썬 객체의 비밀

28. 매직 메서드 - 파이썬 객체의 비밀

print()로 객체를 출력하면 왜 이상한 주소가 나올까? + 연산자를 내 클래스에 쓸 수 있을까? 밑줄 두 개로 감싸인 매직 메서드(던더 메서드)의 비밀을 풀어보자.


매직 메서드란?

매직 메서드(Magic Method)는 __이름__ 형태로, 파이썬이 특정 상황에서 자동으로 호출하는 특별한 메서드다. 던더(dunder) 메서드라고도 부른다 (double underscore).

# 사실 이미 써왔다!
len([1, 2, 3])      # → [1, 2, 3].__len__()
"hello" + " world"  # → "hello".__add__(" world")
3 + 5               # → (3).__add__(5)

우리가 자연스럽게 쓰는 파이썬 문법 대부분이 내부적으로 매직 메서드를 호출하고 있다.

 

매직 메서드 정리


strrepr - 객체 표현

str - 사용자를 위한 문자열

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# __str__ 없이 출력하면?
dog = Dog("멍멍이", 3)
print(dog)   # <__main__.Dog object at 0x...> ← 쓸모없다!

__str__을 정의하면 print()str()에서 사용된다:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name} ({self.age}살)"

dog = Dog("멍멍이", 3)
print(dog)         # 멍멍이 (3살)
print(str(dog))    # 멍멍이 (3살)

repr - 개발자를 위한 표현

__repr__은 주로 디버깅용이다. 객체를 재생성할 수 있는 표현을 반환하는 것이 관례다.

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name} ({self.age}살)"

    def __repr__(self):
        return f"Dog('{self.name}', {self.age})"

dog = Dog("멍멍이", 3)
print(str(dog))     # 멍멍이 (3살)     ← __str__
print(repr(dog))    # Dog('멍멍이', 3)  ← __repr__

# 리스트 안에서는 __repr__이 사용된다
dogs = [Dog("멍멍이", 3), Dog("초코", 5)]
print(dogs)   # [Dog('멍멍이', 3), Dog('초코', 5)]

팁: __str__이 없으면 __repr__이 대신 사용된다. 둘 중 하나만 구현한다면 __repr__을 추천한다.


len - 길이

len() 함수가 호출될 때 실행된다.

class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []

    def add(self, song):
        self.songs.append(song)

    def __len__(self):
        return len(self.songs)

    def __str__(self):
        return f"플레이리스트 '{self.name}' ({len(self)}곡)"

playlist = Playlist("내 즐겨찾기")
playlist.add("좋은 날 - 아이유")
playlist.add("봄날 - BTS")
playlist.add("밤편지 - 아이유")

print(len(playlist))   # 3
print(playlist)        # 플레이리스트 '내 즐겨찾기' (3곡)

eqlt - 비교 연산자

eq - 같은지 비교 (==)

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2)   # True  (좌표가 같으므로)
print(p1 == p3)   # False

lt - 작은지 비교 (<)

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __lt__(self, other):
        return self.score < other.score

    def __repr__(self):
        return f"{self.name}({self.score})"

students = [
    Student("김철수", 85),
    Student("이영희", 92),
    Student("박민수", 78),
]

# __lt__가 있으면 sorted()와 sort()를 쓸 수 있다!
sorted_students = sorted(students)
print(sorted_students)   # [박민수(78), 김철수(85), 이영희(92)]

비교 매직 메서드 종류

메서드 연산자
__eq__ ==
__ne__ !=
__lt__ <
__le__ <=
__gt__ >
__ge__ >=

add - 더하기 연산자

class Money:
    def __init__(self, amount, currency="원"):
        self.amount = amount
        self.currency = currency

    def __add__(self, other):
        if self.currency == other.currency:
            return Money(self.amount + other.amount, self.currency)
        raise ValueError("통화가 다릅니다!")

    def __str__(self):
        return f"{self.amount:,}{self.currency}"

m1 = Money(50000)
m2 = Money(30000)
m3 = m1 + m2        # __add__ 호출

print(m3)   # 80,000원

산술 매직 메서드 종류

메서드 연산자
__add__ +
__sub__ -
__mul__ *
__truediv__ /
__floordiv__ //
__mod__ %
__pow__ **

getitem - 인덱싱과 슬라이싱

[] 연산자를 사용할 수 있게 해준다.

class Deck:
    def __init__(self):
        suits = ["스페이드", "하트", "다이아", "클로버"]
        ranks = ["A"] + [str(n) for n in range(2, 11)] + ["J", "Q", "K"]
        self.cards = [f"{s} {r}" for s in suits for r in ranks]

    def __len__(self):
        return len(self.cards)

    def __getitem__(self, index):
        return self.cards[index]

deck = Deck()
print(len(deck))      # 52
print(deck[0])        # 스페이드 A
print(deck[-1])       # 클로버 K
print(deck[0:3])      # ['스페이드 A', '스페이드 2', '스페이드 3']

# for 문도 자동으로 동작한다!
for card in deck[:5]:
    print(card)

실전 예시: Vector 클래스

모든 매직 메서드를 활용한 완성도 높은 예시를 만들어보자.

class Vector:
    """2D 벡터 클래스"""

    def __init__(self, x, y):
        self.x = x
        self.y = y

    # 문자열 표현
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    # 산술 연산
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    # 비교 연산
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __lt__(self, other):
        return self.magnitude() < other.magnitude()

    # 길이 (크기)
    def __len__(self):
        return 2   # 2D 벡터이므로 항상 2

    def __abs__(self):
        return self.magnitude()

    # 인덱싱
    def __getitem__(self, index):
        if index == 0: return self.x
        elif index == 1: return self.y
        else: raise IndexError("Vector index out of range")

    # 크기 (유틸리티)
    def magnitude(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

# 사용해보기
v1 = Vector(3, 4)
v2 = Vector(1, 2)

# 문자열
print(v1)              # Vector(3, 4)

# 산술
print(v1 + v2)         # Vector(4, 6)
print(v1 - v2)         # Vector(2, 2)
print(v1 * 3)          # Vector(9, 12)

# 비교
print(v1 == Vector(3, 4))  # True
print(v1 < v2)             # False (v1이 더 큼)

# 크기
print(abs(v1))         # 5.0

# 인덱싱
print(v1[0])           # 3
print(v1[1])           # 4

# 정렬도 가능!
vectors = [Vector(3, 4), Vector(1, 1), Vector(2, 3)]
print(sorted(vectors))
# [Vector(1, 1), Vector(2, 3), Vector(3, 4)]

boolcontains

bool - 참/거짓 판단

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add(self, item):
        self.items.append(item)

    def __bool__(self):
        return len(self.items) > 0

    def __len__(self):
        return len(self.items)

cart = ShoppingCart()
print(bool(cart))    # False (비어있음)

if not cart:
    print("장바구니가 비었습니다!")

cart.add("파이썬 책")
print(bool(cart))    # True

if cart:
    print("장바구니에 상품이 있습니다!")

contains - in 연산자

class Team:
    def __init__(self, name):
        self.name = name
        self.members = []

    def add(self, member):
        self.members.append(member)

    def __contains__(self, member):
        return member in self.members

team = Team("파이썬팀")
team.add("김철수")
team.add("이영희")

print("김철수" in team)   # True
print("박민수" in team)   # False

직접 해보기

문제 1. Temperature 클래스를 만들어보자. 섭씨 온도를 저장하고, __str__으로 "25.0°C" 형태로 출력하고, __add__로 두 온도를 더할 수 있게 하자. __lt__로 비교도 가능하게 하자.

문제 2. Stack 클래스를 만들어보자. push(), pop() 메서드와 함께 __len__, __bool__, __str__, __contains__ 매직 메서드를 구현하자.

문제 3. Fraction (분수) 클래스를 만들어보자. 분자(numerator)와 분모(denominator)를 가지고, __add__, __mul__, __str__, __eq__를 구현하자.

문제 4. Matrix2x2 클래스를 만들어보자. 2x2 행렬을 표현하고, __add__(행렬 덧셈), __mul__(스칼라 곱), __str__(보기 좋은 출력), __getitem__(인덱싱)을 구현하자.

정답 보기
# 문제 1 - Temperature
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
‍
    def __str__(self):
        return f"{self.celsius}°C"
‍
    def __add__(self, other):
        return Temperature(self.celsius + other.celsius)
‍
    def __lt__(self, other):
        return self.celsius < other.celsius
‍
    def __eq__(self, other):
        return self.celsius == other.celsius
‍
t1 = Temperature(25)
t2 = Temperature(30)
print(t1)           # 25°C
print(t1 + t2)      # 55°C
print(t1 < t2)      # True
print(sorted([Temperature(30), Temperature(10), Temperature(25)]))
‍
‍
# 문제 2 - Stack
class Stack:
    def __init__(self):
        self.items = []
‍
    def push(self, item):
        self.items.append(item)
‍
    def pop(self):
        if self.items:
            return self.items.pop()
        raise IndexError("빈 스택입니다")
‍
    def __len__(self):
        return len(self.items)
‍
    def __bool__(self):
        return len(self.items) > 0
‍
    def __str__(self):
        return f"Stack({self.items})"
‍
    def __contains__(self, item):
        return item in self.items
‍
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s)          # Stack([1, 2, 3])
print(len(s))     # 3
print(2 in s)     # True
print(s.pop())    # 3
‍
‍
# 문제 3 - Fraction (분수)
class Fraction:
    def __init__(self, num, den):
        self.num = num
        self.den = den
        self._simplify()
‍
    def _gcd(self, a, b):
        while b:
            a, b = b, a % b
        return a
‍
    def _simplify(self):
        g = self._gcd(abs(self.num), abs(self.den))
        self.num //= g
        self.den //= g
‍
    def __add__(self, other):
        new_num = self.num * other.den + other.num * self.den
        new_den = self.den * other.den
        return Fraction(new_num, new_den)
‍
    def __mul__(self, other):
        return Fraction(self.num * other.num, self.den * other.den)
‍
    def __eq__(self, other):
        return self.num == other.num and self.den == other.den
‍
    def __str__(self):
        return f"{self.num}/{self.den}"
‍
f1 = Fraction(1, 3)
f2 = Fraction(2, 3)
print(f1 + f2)          # 1/1
print(Fraction(1, 2) + Fraction(1, 4))  # 3/4
print(Fraction(2, 3) * Fraction(3, 4))  # 1/2
‍
‍
# 문제 4 - Matrix2x2
class Matrix2x2:
    def __init__(self, a, b, c, d):
        self.data = [[a, b], [c, d]]
‍
    def __add__(self, other):
        return Matrix2x2(
            self.data[0][0] + other.data[0][0],
            self.data[0][1] + other.data[0][1],
            self.data[1][0] + other.data[1][0],
            self.data[1][1] + other.data[1][1]
        )
‍
    def __mul__(self, scalar):
        return Matrix2x2(
            self.data[0][0] * scalar,
            self.data[0][1] * scalar,
            self.data[1][0] * scalar,
            self.data[1][1] * scalar
        )
‍
    def __getitem__(self, index):
        row, col = index
        return self.data[row][col]
‍
    def __str__(self):
        return f"| {self.data[0][0]:4} {self.data[0][1]:4} |\n| {self.data[1][0]:4} {self.data[1][1]:4} |"
‍
m1 = Matrix2x2(1, 2, 3, 4)
m2 = Matrix2x2(5, 6, 7, 8)
print(m1 + m2)
# |    6    8 |
# |   10   12 |
print(m1 * 3)
# |    3    6 |
# |    9   12 |
print(m1[0, 1])   # 2

오늘의 정리

매직 메서드 용도
__str__ print(), str() — 사용자용 문자열
__repr__ repr() — 개발자용/디버깅 표현
__len__ len() — 길이/크기 반환
__eq__, __lt__ ==, < 등 비교 연산자
__add__, __sub__ +, - 등 산술 연산자
__getitem__ obj[index] — 인덱싱/슬라이싱
__bool__ bool(), if obj: — 참/거짓 판단
__contains__ in 연산자

다음 편 예고: 데코레이터 - 함수를 꾸미는 함수

함수를 감싸서 기능을 추가하는 데코레이터! @ 기호의 비밀과 실전 활용법을 알아보자.


태그: 파이썬 Python 파이썬독학 매직메서드 던더메서드 __str__ __repr__ 연산자오버로딩 OOP 파이썬중급 IT교육

반응형

댓글