Media Log

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

저작자 표시 비영리 변경 금지
신고
  1. ZiNee at 2013.11.04 02:11 신고 [edit/del]

    뺄셈과 나눗셈, 그리고 정수 나눗셈을 테스트 해봤는데요, __r???__ 일 경우에는 계산이 약간 의외로 나왔습니다.
    좌우가 바뀌어 계산되네요. 이번 강좌도 알차게 배웠습니다. 감사합니다.

    Reply
  2. bellu02 at 2013.11.11 16:19 신고 [edit/del]

    강좌 잘 보고 있습니다.
    감사합니다.

    Reply
  3. 독학생 at 2014.09.16 12:54 신고 [edit/del]

    감사합니다.

    Reply
  4. 322 at 2016.06.18 17:08 신고 [edit/del]

    오버로딩이 아니라 오버라이딩아닌가요

    Reply
  5. navy at 2017.01.03 17:34 신고 [edit/del]

    n + 100 + 100 이런식으로는 안되네요...
    중복계산식에는 못써먹겠네요... 괄호를 사용하면 최종 결과값에 합산한다던가 하는식으로 사용은 가능하지만... n을 기반으로 식을 만들어 나가는건 함수내에 정의하지 않는 다면 힘들게 되네요..
    n과 연산이 되는 과정에서 결과값이 class로 리턴되서 그런거라고 생각되는데...

    재미있는 기능이긴 한데...
    결국 별 의미 없는 기능인듯 합니다...

    일종의 보여주기식의 표현식으로나 쓰인다면 모를까...

    Reply
    • 학생 at 2017.03.04 00:16 신고 [edit/del]

      // navy

      함수에 return; 없어서 그렇습니다. return;을 넣으면 연속된 연산 가능합니다.

      class NumBox() :
      def __init__(self, num):
      self.num = num;
      def __add__(self, num) :
      self.num += num;
      return self.num;
      def __radd__(self, num) :
      self.num += num;
      return self.num;

  6. rnd810 at 2017.06.09 01:01 신고 [edit/del]

    //학생
    return 넣어보니까 n+100+200이면 뒤에 200이 무시되는 것 같은데요...?

    Reply
    • 1학년 at 2017.06.10 18:17 신고 [edit/del]

      return 으로 self를 넣으면 될것같습니다
      def __add__(self,num):
      self.num += num
      return self
      def __radd__(self,num):
      self.num += num
      return self
      sefl.num을 반환하게 되면 말그대로 숫자가 반환되는것같아요.
      이래야 n+100+200 일 때, 앞부분의 n+100을 실행한 후 (그리고 n.num은 100이 증가되어 있을거에요) 다시 n이 반환되서 n+200을 실행할 것 같습니다.

  7. 질문자 at 2017.10.14 07:23 신고 [edit/del]

    class Vector3:
    def __init__(self,x,y,z):
    self.x = x
    self.y = y
    self.z = z
    self.full = [x,y,z]
    def x (self, x):
    self.x = x
    def y(self,y):
    self.y = y
    def z(self,z):
    self.z = z
    def __add__(self,other):
    return [self.x+other.x, self.y+other.y, self.z+other.z]
    def __mul__(self, num):
    return [num*self.x, num*self.y, num*self.z]
    def __rmul__(self,num):
    return [num*self.x, num*self.y, num*self.z]
    def __mul__(self,other):
    return self.x*other.x + self.y*other.y + self.z+other.z



    # 벡터와 벡터의 합
    a = Vector3(1,2,3)
    b = Vector3(4,5,6)
    print(a+b)

    # 벡터와 스칼라의 곱
    print(3*a)
    print(a*3) #이녀석이 왜 에러가나죠?

    #벡터와 벡터의곱(내적)
    print(a*b)



    Reply
    • BlogIcon EXYNOA at 2017.10.14 18:01 신고 [edit/del]

      파이썬은 메소드 오버로딩을 지원하지 않습니다. 하나의 클래스 내에는 하나의 메소드가 있어야 합니다. 다만, 오버로딩을 흉내낼 수 있는 다양한 기법들이 존재합니다.

      아래처럼 파이썬의 내장 함수인 isinstance()를 사용하여 해당 객체가 특정 클래스의 인스턴스라면 각자 다른 행동을 할 수 있도록 코드를 작성할 수 있습니다.

      def __add__(self,other):
      return [self.x+other.x, self.y+other.y, self.z+other.z]
      def __mul__(self, other):
      if isinstance(other, self.__class__):
      return self.x*other.x + self.y*other.y + self.z*other.z
      elif isinstance(other, int):
      return [self.x*other, self.y*other, self.z*other]
      else:
      raise TypeError("unsupported operand type(s) for +: '{}' and '{}'").format(self.__class__, type(other))
      __rmul__ = __mul__
      ...
      결과:
      [5, 7, 9]
      [3, 6, 9]
      [3, 6, 9]
      32

submit

티스토리 툴바