코딩

[Python] Magic method 이해하기

jyseo-me 2025. 1. 26. 01:16

Magic method (or Dunder method)

Python에는 Double underscore, "__"로 둘러싸인 특별한 method인 magic method가 있다.

(대표적으로 생성자 __init__()이 있다.)

 

Python의 type인 int, str, list는 모두 class이기 때문에 method를 가지고 있는데, 내장함수 dir() 함수를 사용하면 이를 모두 볼 수 있다.

 

Ex) Str의 method를 모두 출력해보자.

>> print(dir(str))
>> ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', ..., 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

 

흔히 사용하는 split, join과 같은 method도 있지만, "__"로 둘러싸인 magic method가 더 많은 것을 확인할 수 있다.


Magic method를 사용하는 이유?

Magic method를 사용하면 생성된 객체를 직관적인 코드로 다룰 수 있다. Magic method가 특정 상황에서 자동으로 호출되며, 이 동작을 customize할 수 있다.

Ex)

- len(객체)를 호출하면 __len__ method가 자동으로 호출

- __add__ 등 연산자를 overloading하면 객체 간 연산을 +, - 와 같은 연산자로 편하게 할 수 있다.

 

cf)

아래 파이썬 FAQ에서 어떤 기능은 method로 구현하고, 어떤 기능은 함수로 구현하는 이유를 설명하고 있다.

https://docs.python.org/3/faq/design.html#why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list

 

Design and History FAQ

Contents: Design and History FAQ- Why does Python use indentation for grouping of statements?, Why am I getting strange results with simple arithmetic operations?, Why are floating-point calculatio...

docs.python.org

Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

The major reason is history. Functions were used for those operations that were generic for a group of types and which were intended to work even for objects that didn’t have methods at all (e.g. tuples). It is also convenient to have a function that can readily be applied to an amorphous collection of objects when you use the functional features of Python (map(), apply() et al).
In fact, implementing len(), max(), min() as a built-in function is actually less code than implementing them as methods for each type. One can quibble about individual cases but it’s a part of Python, and it’s too late to make such fundamental changes now. The functions have to remain to avoid massive code breakage.

 

요약하면 이는 Python 발전 역사의 흔적이다. 과거 method가 없던 type에도 사용하기 위해 함수로 구현된 것도 있는 것이며, map 등 몇몇 경우에서 유리하다. 지금 와서 바꾸기에는 혼란이 크다고 한다...


대표적인 예시

딥러닝에서도 custom class를 정의하면서 magic method를 overloading하는 경우가 많다. 자주 사용되는 magic method들의 쓰임을 하나씩 알아보자.

1. __init__

이미 잘 알고 있는 생성자, 객체를 초기화 할 때 사용 

호출 시점: 인스턴스가 생성될 때 자동으로 호출

2. __call__

주로 network의 forward pass를 정의할 때 사용하며, 인스턴스를 함수처럼 호출할 수 있게 해

호출 시점: 객체를 함수처럼 호출

 

Ex)

class Square:
    def __call__(self, x):
        return x**2

# S를 Square 인스턴스로 생성 
S = Square()

# S를 함수처럼 사용할 수 있음!!
>> S(3)
>> 9

3. __len__

객체의 길이를 반환하기 위해 사용

호출 시점: len(객체)

 

Ex)

class Dataset:
    def __len__(self):
    	# Dataset의 길이 반환
        return len(self.data)

4. __getitem__, __setitem__, __delitem__

__getitem__: 인덱싱을 통해 항목 가져오기

__setitem__: 인덱싱을 통해 항목 바꾸기

__delitem__: 인덱싱을 통해 항목 삭제

 

호출 시점: 객체[idx]로 불러올 때 호출

 

Ex)

class Dataset:
    def __getitem__(self, idx):
        return self.data[idx]
    
    def __setitem__(self, idx, value):
    	return self.data[idx] = value
    
    def __delitem__(self, idx):
    	del self.data[idx]

5. __getattr__, __setattr__

객체의 속성에 동적으로 접근할 수 있음

호출 시점

- __getattr__: 존재하지 않는 속성에 접근할 때 fallback으로 호출

- __setattr__: 객체의 속성을 설정할 때 호출

 

Ex)

class Example:
    x = 10  # 클래스 변수

    def __getattr__(self, name):
        print(f"__getattr__: No attribute {name}")
        return f"return: {name}"

    def __setattr__(self, name, value):
        print(f"__setattr__: Set {name} to {value}")
   
ex = Example()

>> ex.y
>> __getattr__: No attribute {name}

>> ex.x = 15
>> __setattr__: Set x to 15

6. 사칙 연산자: __add__, __sub__, __mul__, __truediv__
비교 연산자: __eq__, __ne__, __lt__, __gt__

사칙연산자 (+, -, *, /), 비교연산자 (==, !=, <, >)를 사용하여 객체간의 연산이 가능하다.

 

Ex)

class Tensor:
    def __init__(self, data):
        self.data = data
    
    def __mul__(self, other):
        return Tensor(self.data * other.data)

a = Tensor(3)
b = Tensor(5)

>> a * b # __mul__ 호출
>> Tensor(15)

7. __iter__, __next__

__iter__: 해당 객체를 반복 가능한 객체인 iterable로 만들어줌, iter(객체)로 iterator를 호출할 수 있음

호출 시점: iter(객체)

__next__: iterable의 다음 값을 반환

호출 시점: for문, while문, next(객체)

 

Ex) next 호출 시 역방향으로 값을 호출하는 reverse iterator

class ReverseItertor:
    def __init__(self, data):
        self.data = data
        self.position = len(self.data) -1
	
    # __iter__는 self만 return해도 됨!
    def __iter__(self):
        return self

    def __next__(self):
        if self.position < 0:
            raise StopIteration
        result = self.data[self.position]
        self.position -= 1
        return result

 

cf) for 문의 작동 원리

for x in y:

(1) iter() 호출: y.__iter__()이 호출되어 iterator 반환

(2) __next__ 호출: 항목을 하나씩 가져옴

 

* iterable: __iter__가 있음, iterator: __iter__, __next__가 있음 (list는 iterable이지만, iterator가 아니다!)

* iterable은 반복 가능한 객체, iterator는 next로 다음 값을 return하는 객체

* next가 호출되다가 다음 값이 없을 때 StopIteration error

8. __repr__ , __str__

객체에 대한 설명을 적음. 객체를 print하면 설명이 나오는 경우가 있다. 이는 class의 __repr__ / __str__ 덕분이다.

__repr__: 객체에 대한 공식 string description

__str__: 객체에 대한 비공식 string description (더 쉬운 설명!)

호출 시점: print(객체), repr(객체), str(객체)


이외에도

- __enter__, __exit__: with 문 enter/exit 시 호출, 

__hash__: 객체의 hash 값 반환

등 다양한 magic method가 있음!

'코딩' 카테고리의 다른 글

[Python] Module 완벽 정리  (0) 2025.01.30