끝나지 않는 프로그래밍 일기

1. 연산자 오버로딩(Operator Overloading)

 

이번에는 연산자 오버로딩(Operator Overloading)에 대해서 알아보려고 합니다. 이 연산자 오버로딩이란, 인스턴스 객체끼리 서로 연산을 할 수 있게끔 기존에 있는 연산자의 기능을 바꾸어 중복으로 정의하는 것을 말합니다. 예를 들어보자면, 아래와 같은 경우를 생각해 볼 수 있겠죠?

>>> class NumBox:
	def __init__(self, num):
		self.Num = num
	
>>> n = NumBox(40)
>>> n + 100
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    n + 100
TypeError: unsupported operand type(s) for +: 'NumBox' and 'int'

위 예제를 보시면, 인스턴스 객체 n에 '+' 연산자를 사용하여 100을 더하려는 코드가 보이는데 이는 지원되지 않는 연산 타입이므로 NumBox와 int간의 연산을 수행하기 힘들다는 것입니다. 그렇다면 + 연산자를 사용하여 성공적으로 클래스 NumBox 내에 있는 변수 Num의 값을 증가시키려면 어떻게 해야 할까요? 바로 우리가 배우게 될 연산자 오버로딩이란 기법을 이용하면 됩니다. 파이썬에서는 인스턴스 객체의 연산을 위해 여러가지 연산자를 미리 정의해두었는데, 이를 표로 정리하여 아래에 작성해두었습니다.


메서드(Method)

연산자(Operator)

사용 예

__add__(self, other)

+ (이항)

A + B, A += B

__pos__(self)

+ (단항)

+A

__sub__(self, other)

- (이항)

A - B, A -= B

__neg__(self)

- (단항)

-A

__mul__(self, other)

*

A * B, A *= B

__truediv__(self, other)

/

A / B, A /= B

__floordiv__(self, other)

//

A // B, A //= B

__mod__(self, other)

%

A % B, A %= B

__pow__(self, other)

pow(), **

pow(A, B), A ** B

__lshift__(self, other)

<<

A << B, A <<= B

__rshift__(self, other)

>>

A >> B, A >>= B

__and__(self, other)

&

A & B, A &= B

__xor__(self, other)

^

A ^ B, A ^= B

__or__(self, other) 

|

A | B, A |= B

__invert__(self)

~

~A

__abs__(self)

abs()

abs(A)

<미리 정의된 수치 연산자>


파이썬에서 미리 정의된 함수를 중복 정의하여 우리가 정의한 동작을 수행하게 하도록 합시다. 아래 예제에서는 +와 - 연산자를 오버로딩하여 인스턴스 객체에 연산자를 사용합니다. 천천히 살펴봅시다.

>>> class NumBox:
	def __init__(self, num):
		self.Num = num
	def __add__(self, num):
		self.Num += num
	def __sub__(self, num):
		self.Num -= num

>>> n = NumBox(40)
>>> n + 100
>>> n.Num
140
>>> n - 110
>>> n.Num
30

위 예제를 보시면 클래스 NumBox 내에 미리 정의된 함수인 __add__, __sub__를 중복 정의하였습니다. 만약, 우리의 예상대로라면 NumBox의 인스턴스 객체에 + 연산자가 쓰였을때는 클래스 내에 있는 Num의 값이 지정한 수만큼 증가하여야 하며, - 연산자가 쓰였을때는 Num의 값이 지정된 수만큼 감소하여야 합니다. 10행을 보시면 인스턴스 객체 n에 100을 더하는 연산을 하고 있으며, 그 후 n.Num의 값을 보자 40에서 100이 증가된 140이란 결과값을 확인하실 수 있습니다. 마찬가지로 13행에서도, 인스턴스 객체 n에 110을 빼는 연산을 하고 있으며 그 후 n.Num의 값이 140에서 110이 감소되어 30이란 값을 확인하실 수 있습니다.


한가지 더 알아보자면, 위 예제에서 'n + 100'과 같은 연산은 사실상 우리가 중복 정의한 함수가 호출되는 것입니다. 이런 연산은 아래와 같이 호출된다고 생각하시면 됩니다.

n.__add__(100)

만약에, 피연산자의 순서를 앞뒤로 바꾸어도 우리가 원하는 결과값을 얻어낼 수 있을까요? 한번 아래의 예제를 통해 알아보도록 하겠습니다.

>>> 110 + n
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    110 + n
TypeError: unsupported operand type(s) for +: 'int' and 'NumBox'

이게 왠일입니까? 피연산자의 순서를 바꾸었더니 우리가 원하는 결과값은 온데간데 없고, 지원되지 않는 연산 타입이란 타입 에러만 떡하니 자리를 차지하고 있습니다. 왜 이런가하니, 위와 같이 인스턴스 객체가 오른쪽으로 이동하면 __add__ 함수가 호출되는게 아니라 __radd__ 함수가 호출되기 때문입니다. 그렇기 때문에, __radd__도 역시 정의해 주어야 위와 같은 연산에서 에러가 발생하지 않습니다.


이렇게 피연산자의 순서가 뒤바뀐 경우에는 아래와 같이 연산자 이름앞에 'r'을 붙여주면 됩니다. 예를들면, 아래와 같이 말이죠.

__add__ = __radd__
__sub__ = __rsub__
__mul__ = __rmul__

이제 왜 에러가 발생하는지 알았으니, 위와 같이 앞에 'r'이 붙은 연산자를 정의하여 예제를 한번 고쳐보도록 하겠습니다. 

>>> class NumBox:
	def __init__(self, num):
		self.Num = num
	def __add__(self, num):
		self.Num += num
	def __radd__(self, num):
		self.Num += num

>>> n = NumBox(100)
>>> 120 + n
>>> n.Num
220
>>> 300 + n
>>> n.Num
520

위 예제를 보시면 클래스 NumBox 내에 __radd__가 중복 정의된 것을 보실 수 있습니다. 10행에서 피연산자의 순서를 바꾸어 + 연산을 진행하고 있는데, 아무런 에러 없이 우리가 생각하던 결과를 표시하고 있습니다. 독자분들도 위의 표에 있는 함수를 중복 정의하여 여러가지 예제를 만들어가면서 연산자 오버로딩에 대한 경험을 쌓고 이해를 해보시는걸 권장합니다.