Object-Oriented Programming - Magic Methods

Contents

  • Operator Overloading
  • Magic Methods

Operator Overloading

첫 코딩 언어로 파이썬이 많이 추천되는 이유는 간단하고 직관적인 문법 덕이다.

a = 1
b = 2
c = [2,2]
d = ['hello',3]
h = 'h'
i = 'i'

>>> print(a+b)
3
>>> print(c+d)
[2, 2, 'hello', 3]
>>> print(h+i)
hi

파이썬에선 + 연산으로 다양한 자료구조를 더할 수 있다. 가장 직관적으로 숫자를 더할 수 있고, 나아가 리스트와 문자열도 더해준다. 하지만 같은 + 연산으로 각 자료구조가 더해지는 방식은 다르다. 두개의 리스트는 하나의 리스트로 통합되고, 두개의 문자열은 하나로 이어진다. 이는 operator overloading 이란 개념으로 작동된다.

>>> type(a)
<class 'int'>
>>> dir(a)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__']

>>> type(h)
<class 'str'>
>>> dir(h)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__']

위 예시를 보자. a, h 는 각각 int, str 타입의 자료구조다. 이때 dir() 함수에 변수를 인자로 넘기면 실제로 해당 자료구조가 실행 할 수 있는 연산들이 출력된다.

>>> a+b
3
>>> a.__add__(b)
3
>>> a+b == a.__add__(b)
True

즉, 두 정수를 더하는 연산인 a+b 는 사용자의 편의를 위한 연산문이며, 실제로 코드상으로 실행되는 연산은 a.__add__(b) 인 것이다. 두 연산의 bool operand 확인 시 True 가 이를 증명한다.

하지만 이때 dir() 의 출력문을 자세히 보자. 정수인 a 와 문자열인 h 는 둘다 __add__ 라는 메서드가 존재하고, 이는 객체지향적 관점의 메서드가 맞다. 파이썬의 모든 자료구조는 파이썬 내부의 클래스로 정의된 것이며, int, str 각각 독립적인 클래스다. __add__ 는 각 클래스 속 정의된 메서드이며, 이렇게 double underscore 을 지닌 메서드를 매직 메서드, 혹은 dunder method 라고도 한다.

이런 매직 메서드는 각 연산자가 작동하는 방식을 ‘오버로딩’, 상황에 맞게 바꾸는 역할을 한다. 즉, 정수에 + 연산자를 수행 할 때 int 클래스 내부에 정의된 __add__ 메서드가 수행되는 것이다. int 클래스와 str 클래스 내부에 __add__ 메서드의 리턴값이 다르기 때문에 같은 + 연산자를 수행해도 자료구조에 따라 결과가 다른 것이다.

매직 메서드를 통한 연산자 오버로딩으로 우린 클래스의 인스턴스를 자유롭게 사용할 수 있다.

Magic Methods

파이썬엔 각 연산자에 해당되는 다양한 매직 메서드가 있다. 대표적으로 + 는 __add__, - 는 __sub__ 등이 있으며, 파이썬의 builtin 함수들도 오버로딩 할 수 있는 매직 메서드가 존재한다.

class Student:

    def __init__(self, first, last, health):
        self.first = first
        self.last = last
        self.health = health
        self.email = first + '.' + last + '@korea.ac.kr'

    def fullname(self):
        return f'{self.first} {self.last}'

stu_1 = Student('Tom', 'Lee', 100)
stu_2 = Student('Ian', 'Goodfellow', 100)
>>> print(stu_1)
<__main__.Student object at 0x10158a670>

위 예시를 보자. stu_1print() 하면 해당 인스턴스의 메모리 위치가 출력된다. 하지만 print 는 파이썬에서 가장 많이 사용되는 함수 중 하나이며, 클래스의 사용자 또한 가장 많이 찾는 함수이다. 그렇기에 인스턴스를 print 할때 메모리 위치가 아닌, 인스턴스의 중요한 정보를 출력하도록 할 수 있을까?

class Student:

    def __init__(self, first, last, health):
        self.first = first
        self.last = last
        self.health = health
        self.email = first + '.' + last + '@korea.ac.kr'

    def fullname(self):
        return f'{self.first} {self.last}'

    def __repr__(self):
        return f'Instance of Class Student: {self.fullname()}, {self.email}'

stu_1 = Student('Tom', 'Lee', 100)
stu_2 = Student('Ian', 'Goodfellow', 100)

>>> print(stu_1)
Instance of Class Student: Tom Lee, Tom.Lee@korea.ac.kr

__repr__ 메서드를 추가하자 print 가 작동하는 방식이 바뀌었다. __repr__ 는 파이썬의 print 함수를 오버로딩 해주는 매직 메서드다. 하지만 보통 __repr____str__ 와 같이 사용된다.

class Student:

    def __init__(self, first, last, health):
        self.first = first
        self.last = last
        self.health = health
        self.email = first + '.' + last + '@korea.ac.kr'

    def fullname(self):
        return f'{self.first} {self.last}'

    def __repr__(self):
        return f'Instance of Class Student: {self.fullname()}, {self.email}'

    def __str__(self):
        return f'{self.fullname()}, {self.email}'

stu_1 = Student('Tom', 'Lee', 100)
stu_2 = Student('Ian', 'Goodfellow', 100)

>>> print(stu_1)
Tom Lee, Tom.Lee@korea.ac.kr

__str__ 메서드를 클래스에 추가하니 print 문이 __str__ 에서 정의된 값으로 리턴되도록 바뀌었다. 얼핏 보면 __str____repr__ 는 같은 원리로 동작하기에 하나만 정의해도 될 것 같지만, 보통 클래스를 정의할 때 두 메서드 다 정의한다.

보통 __repr__ 는 디버깅과 로깅의 용도로 리턴값을 정의하며, __str__ 는 사용자 측면에서 인스턴스를 이해하기 쉽게 돕는 용도로 쓰인다. 그렇기에 __str__ 메서드는 필수가 아니지만, __repr__ 메서드는 클래스를 정의할 때 무조건적으로 정의하는 것이 좋다.

My rule of thumb: __repr__ is for developers, __str__ is for customers.