PyTorch를 쓰려고 하다보니 class 객체를 정의할 일이 많아졌고, 문득 __init__ 이런 방식으로 독특하게 메소드를 정의하는 이유가 궁금해져서 알아보았다.
클래스 안에 정의된 여러 메소드(함수) 중에서도 양 옆에 언더바_를 두 개씩 붙이는(던더라고 한다) 메소드를 매직 메소드 또는 특별 메소드라고 한다.
가장 접하기 쉬운 매직 메소드로는 __init__이 있다.
매직 메소드는 Python에서 특정 동작을 정의하기 위해 특별히 예약된 메소드들이다.
매직 메소드들은 예약되어 있는 특정한 기능을 클래스도 사용할 수 있도록 해서 내가 생성한 클래스 객체와 연결되어 있지 않은 내장 함수나 연산자와의 상호작용을 가능하게 한다.
예를 들어, __len__ 이라는 매직 메소드에는, 파이썬 내장함수인 len() 함수를 사용할 수 있도록 사전에 예약되어 있는 것이다.
또 다른 예시로, 클래스 객체에 인덱싱을 구현하고 싶다면 -> __getitem__ 이라는 매직 메소드를 써서 인덱싱을 하도록 클래스를 지원할 수 있다.
이처럼, 매직 메소드는 몇 가지 기능을 구현할 수 있도록 사전에 내장되어 있는 메소드다.
그래서 매직 메소드의 존재를 모른 채로 클래스를 정의하는 것은 비효율적일 수 있다.
만약, 내가 생성하고자 하는 객체가 인덱싱이 되어야하는 객체인데, 매직 메소드를 알지 못해 이를 직접 정의하는 것은, 간단하고 직관적인 매직 메소드를 사용하는 것보다 훨씬 비효율적일 수밖에 없기 때문이다.
그렇다면, 내가 만들고자 하는 객체가 인덱싱이 되는 객체이고 len도 사용할 수 있어야 하는 객체라고 해서 기존에 존재하는 list 클래스를 상속받는 것은 현명한 것일까?
이 역시 현명하지 않다.
왜냐하면 list 클래스를 상속받게 되면서 상속받은 모든 메소드를 사용하게 되기 때문이다.
이는, 내가 정의한 객체에게 불필요한 메소드가 포함될 수도 있어 명확한 객체 정의에 어긋나게 된다.
매직 메소드와 연결된 기능을 아는 것은 효율적인 클래스 생성에 도움이 될 수 있다.
따라서, 주요 매직 메소드를 알아보자.
1. 객체 초기화 및 표현
- \__init\__(self, ...): 객체가 생성될 때 호출되며, 초기 상태를 설정
- \__str\__(self): 객체의 비공식적인 문자열 표현을 반환. print() 함수와 str() 함수에서 사용됨
- \__repr\__(self): 객체의 공식적인 문자열 표현을 반환. repr() 함수와 인터프리터에서 사용됨
(※ repr의 공식적인 문자열 표현이라는 것은, 개발자가 객체를 이해하고 디버깅하는 데 도움이 되는 정보를 말하며, repr()함수로 반환된 문자를 만약 eval()함수에 넣었을 때 기존의 객체와 동일한 객체를 생성할 수 있도록 하는 특징을 갖는다.)
2. 인덱싱 및 반복
- \__getitem\__(self, key): 객체의 특정 항목에 접근할 수 있도록 함. 인덱싱 연산([])에 사용
- \__setitem\__(self, key, value): 객체의 특정 항목을 설정할 수 있도록 함. 인덱싱을 통한 값 할당([] =)에 사용
- \__delitem\__(self, key): 객체의 특정 항목을 삭제할 수 있게 함. del 연산자에 사용
- \__iter\__(self): 객체가 반복(iteration)될 수 있도록 함. 반복자(iterator)를 반환
- \__next\__(self): 반복자에서 다음 항목을 반환
3. 연산자 오버로딩
- \__add\__(self, other): + 연산자를 오버로딩
- \__sub\__(self, other): - 연산자를 오버로딩
- \__mul\__(self, other): * 연산자를 오버로딩
- __truediv__(self, other): / 연산자를 오버로딩
4. 비교 연산
- \__eq\__(self, other): == 연산자를 오버로딩
- \__ne\__(self, other): != 연산자를 오버로딩
- \__lt\__(self, other): < 연산자를 오버로딩
- \__le\__(self, other): <= 연산자를 오버로딩
- \__gt\__(self, other): > 연산자를 오버로딩
- \__ge\__(self, other): >= 연산자를 오버로딩
5. 길이 표현
- \__len\__(self, other): 객체의 길이를 반환. len() 함수에서 사용됨
이외에도 __call__, __enter__, __exit__ 등 다양한 매직 메소드가 존재한다.
__call__ 같은 경우는 객체를 함수처럼 호출할 수 있도록 한다.
class MyClass:
def __init__(self, value):
self.value = value
def __call__(self, x):
return self.value * x
obj = MyClass(10)
# obj라는 MyClass 객체를 함수처럼 사용해 뒤에 괄호를 붙이면 obj() 함수처럼 호출됨
print(obj(2)) # 출력: 20
매직 메소드를 이해하면 클래스에 대해서 조금 더 이해하기 수월할 것이다.