20. 클래스와 연산자 중복 정의




1 연산자 중복 (Operator Overloading)


1-1 수치 연산자 중복

  • 직접 정의하는 클래스 인스턴스에 연산자를 적용하기 위하여 미리 약속되어 있는 메소드들을 정의

메소드(Method)

연산자(Operator)

인스턴스 o에 대한 사용 예

\_\_add\_\_(self, B)

+ (이항)

o + B, o += B

\_\_sub\_\_(self, B)

- (이항)

o - B, o -= B

\_\_mul\_\_(self, B)

\*

o \* B, o \*= B

\_\_div\_\_(self, B)

/

o / B, o /= B

\_\_floordiv\_\_(self, B)

//

o // B, o //= B

\_\_mod\_\_(self, B)

%

o % B, o %= B

\_\_divmod\_\_(self, B)

divmod()

divmod(o, B)

\_\_pow\_\_(self, B)

pow(), \*\*

pow(o, B), o \*\* B

\_\_lshift\_\_(self, B)

<<

o << B, o <<= B

\_\_rshift\_\_(self, B)

>>

o >> B, o >>= B

\_\_and\_\_(self, B)

&

o & B, o &= B

\_\_xor\_\_(self, B)

^

o ^ B, o ^= B

\_\_or\_\_(self, B) 

|

o | B, o |= B

\_\_neg\_\_(self)

- (단항)

-A

\_\_abs\_\_(self)

abs()

abs(o)

\_\_pos\_\_(self)

+ (단항)

+o

\_\_invert\_\_(self)

~

~o

In [1]:
class Integer:
    def __init__(self, i):
        self.i = i
    def __str__(self):
        return str(self.i)
    def __add__(self, other):
        return self.i + other

i = Integer(10)
print i
print str(i)

print
i = i + 10
print i

print
i += 10
print i
10
10

20

30
In [2]:
class MyString:
    def __init__(self, str):
        self.str = str
    def __div__(self, sep): # 나누기 연산자 /가 사용되었을 때 호출되는 함수 
        return self.str.split(sep) # 문자열 self.str을 sep를 기준으로 분리

m = MyString("abcd_abcd_abcd")
print m / "_"
print m / "_a"

print
print m.__div__("_")
['abcd', 'abcd', 'abcd']
['abcd', 'bcd', 'bcd']

['abcd', 'abcd', 'abcd']
  • 연산자 왼쪽에 피연산자, 연산자 오른쪽에 객체가 오는 경우
    • 메소드 이름 앞에 r이 추가된 메소드 정의
In [3]:
class MyString:
    def __init__(self, str):
        self.str = str
    def __div__(self, sep):
        return str.split(self.str, sep)
    __rdiv__ = __div__

m = MyString("abcd_abcd_abcd")
print m / "_"
print m / "_a"
print
print "_" / m
print "_a" / m
['abcd', 'abcd', 'abcd']
['abcd', 'bcd', 'bcd']

['abcd', 'abcd', 'abcd']
['abcd', 'bcd', 'bcd']
In [4]:
class MyString:
    def __init__(self, str):
        self.str = str
    def __div__(self, sep):
        return str.split(self.str, sep)
    __rdiv__ = __div__
    def __neg__(self):
        t = list(self.str)
        t.reverse()
        return ''.join(t)
    __invert__ = __neg__
    
m = MyString("abcdef")
print -m
print ~m
fedcba
fedcba

1-2 비교 연산자 중복

  • 각각의 비교 연산에 대응되는 메소드 이름이 정해져 있지만 그러한 메소드가 별도로 정의되어 있지 않으면 cmp가 호출됨
메소드 연산자 비고
__cmp__(self, other) 아래 메소드가 부재한 상황에 호출되는 메소드
__lt__(self, other) self < other
__le__(self, other) self <= other
__eq__(self, other) self == other
__ne__(self, other) self != other
__gt__(self, other) self > other
__ge__(self, other) self >= other
  • 객체 c에 대한 c > 1연산의 행동 방식
    • c.__gt__()가 있다면 호출 결과을 그대로 반환
    • 정의된 c.__gt__()가 없고, __cmp__() 함수가 있을 경우
      • c.__cmp__() 호출 결과가 양수이면 True 반환, 아니면 False 반환
In [5]:
class MyCmp:
    def __cmp__(self, y):
        return 1 - y
    
c = MyCmp() 
print c > 1 # c.__cmp__(1)을 호출, 반환값이 양수이어야 True
print c < 1 # c.__cmp__(1)을 호출, 반환값이 음수이어야 True
print c == 1 # c.__cmp__(1)을 호출, 반환값이 0이어야 True
False
False
True
  • 객체 m에 대한 m < 10연산의 행동 방식
    • m.__lt__()가 있다면 호출 결과을 그대로 반환
    • 정의된 m.__lt__()가 없고, __cmp__() 함수가 있을 경우
      • m.__cmp__() 호출 결과가 음수이면 True 반환, 아니면 False 반환
In [6]:
class MyCmp2:
    def __lt__(self, y):
        return 1 < y

m = MyCmp2()
print m < 10 # m.__lt__(10)을 호출
print m < 2
print m < 1
True
True
False
  • 객체 m에 대한 m == 10연산의 행동 방식
    • m.__eq__()가 있다면 호출 결과을 그대로 반환
    • 정의된 m.__eq__()가 없고, __cmp__() 함수가 있을 경우
      • m.__cmp__() 호출 결과가 0이면 True 반환, 아니면 False 반환
In [7]:
class MyCmp3:
    def __eq__(self, y):
        return 1 == y

m = MyCmp3()
print m == 10 # m.__eq__(10)을 호출
m1 = MyCmp3()
print m == 1

class MyCmp4:
    def __init__(self, value):
        self.value = value
    def __cmp__(self, other):
        if self.value == other:
            return 0
m2 = MyCmp4(10)
print m2 == 10
False
True
True

2 시퀀스/매핑 자료형의 연산자 중복


  • 클래스를 개발할 때 다음 메소드들을 적절하게 구현하면 자신만의 시퀀스 자료형을 만들 수 있음
  • 시퀀스 자료형 및 매핑 자료형을 위해 구현이 필요한 메소드
메소드 연산자
__len__(self) len()
__contains__(self, item) item in self
__getItem__(self, key) self[key]
__setItem__(self, key, value) self[key] = value
__delItem__(self, key) del self(key)

2-1 인덱싱 (Indexing)

  • len(s1) --> s1.__len__() 메소드 호출
  • sl[4] --> s1.__getitem__(4) 호출
  • IndexError
    • 시퀀스 자료형이 범위를 벗어난 인덱스 참조 요구시에 발생시킴
    • 리스트, 튜플, 문자열등에서도 동일한 조건에서 발생됨
In [8]:
class Square:
    def __init__(self, end):
        self.end = end
    def __len__(self):
        return self.end
    def __getitem__(self, k):
        if k < 0 or self.end <= k: 
            raise IndexError, k
        return k * k
    
s1 = Square(10)
print len(s1) # s1.__len__()
print s1[1] #s1.__getitem__(1)
print s1[4]
print s1[20]
10
1
16
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-8-78c6c0117c4f> in <module>()
     13 print s1[1] #s1.__getitem__(1)
     14 print s1[4]
---> 15 print s1[20]

<ipython-input-8-78c6c0117c4f> in __getitem__(self, k)
      6     def __getitem__(self, k):
      7         if k < 0 or self.end <= k:
----> 8             raise IndexError, k
      9         return k * k
     10 

IndexError: 20
  • 다음 for 문은 s1에 대해 __getitem()__ 메소드를 0부터 호출하여 IndexError가 발생하면 루프를 중단한다.
In [9]:
for x in s1:
    print x,
0 1 4 9 16 25 36 49 64 81
  • __getitem__() 메소드가 정의되어 있다면 다른 시퀀스 자료형으로 변환이 가능
In [10]:
print list(s1)
print tuple(s1)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
  • 위에서 알 수 있듯이 파이썬은 내장 자료형과 개발자가 정의한 자료형에 대해 일관된 연산 적용이 가능
    • 파이썬 언어의 장점: 일관된 코딩 스타일 유지

2-2 매핑 자료형의 연산자 중복

In [11]:
class MyDict:
    def __init__(self, d = None):
        if d == None: d = {}
        self.d = d
    def __getitem__(self, k): #key
        return self.d[k]
    def __setitem__(self, k, v):
        self.d[k] = v
    def __len__(self):
        return len(self.d)
    
m = MyDict()            #__init__호출
m['day'] = 'light'      #__setitem__호출
m['night'] = 'darkness' #__setitem__호출 
print m
print m['day']          #__getitem__호출
print m['night']        #__getitem__호출
print len(m)            #__len__호출
<__main__.MyDict instance at 0x103cc7ab8>
light
darkness
2
In [12]:
class MyDict:
    def __init__(self, d=None):
        if d == None: d = {}
        self.d = d
    def __getitem__(self, k):
        return self.d[k]
    def __setitem__(self, k, v):
        self.d[k] = v
    def __len__(self):
        return len(self.d)
    def keys(self):
        return self.d.keys()
    def values(self):
        return self.d.values()
    def items(self):
        return self.d.items()
    
m = MyDict({'one':1, 'two':2, 'three':3})
print m.keys()
print m.values()
print m.items()
['three', 'two', 'one']
[3, 2, 1]
[('three', 3), ('two', 2), ('one', 1)]

3 문자열 변환과 호출 가능 객체


3-1 문자열로 변환하기

1) __repr__

  • 객체를 대표하여 유일하게 표현할 수 있는 공식적인 문자열
  • eval() 함수에 의하여 같은 객체로 재생성 될 수 있는 문자열 표현

2) __str__

  • 객체의 비공식적인 문자열 표현
  • 사용자가 보기 편한 형태로 자유롭게 표현될 수 있음
In [13]:
class StringRepr:
    def __repr__(self):
        return 'repr called'
    def __str__(self):
        return 'str called'
    
s = StringRepr()
print s
print str(s)
print repr(s)
print `s`
str called
str called
repr called
repr called
  • __str__() 호출시
    • __str__()가 정의되어 있지 않으면 __repr__()이 대신 호출됨
In [14]:
class StringRepr:
    def __repr__(self):
        return 'repr called'
    
s = StringRepr()
print s
print repr(s)
print str(s)
print `s`
repr called
repr called
repr called
repr called
  • __repr__() 호출시
    • __repr__()이 정의되어 있지 않으면 객체 식별자가 출력됨
    • 대신하여 __str__()이 호출되지 않음
In [15]:
class StringRepr:
    def __str__(self):
        return 'str called'

s = StringRepr()
print s
print repr(s)
print str(s)
print `s`
str called
<__main__.StringRepr instance at 0x103cc7f80>
str called
<__main__.StringRepr instance at 0x103cc7f80>

3-2 호출 가능한 클래스 인스턴스 만들기

  • 클래스 인스턴스에 __call__ 메소드가 구현되어 있다면 해당 인스턴스는 함수와 같이 호출될 수 있다.
    • 인스턴스 x에 대해 x(a1, a2, a3)와 같이 호출된다면 x.__call__(a1, a2, a3)가 호출된다.
In [16]:
class Accumulator:
    def __init__(self):
        self.sum = 0
    def __call__(self, *args):
        self.sum += sum(args)
        return self.sum
    
acc = Accumulator()
print acc(1,2,3,4,5)
print acc(6)
print acc(7,8,9)
print acc.sum
15
21
45
45
In [17]:
class Accumulator:
    def __init__(self):
        self.sum = 0
    def __call__(self, *args):
        self.sum = reduce(lambda x, y: x + y, args)
        return self.sum
    
acc = Accumulator()
print acc(1,2,3,4,5)
print acc(6)
print acc(7,8,9)
print acc.sum
15
6
24
24
  • 호출 가능 객체인지 알아보기
In [18]:
def check(obj):
    if callable(obj):
        print 'callable'
    else:
        print 'not callable'

class B:
    def func(self, v):
        return v
    
class A:
    def __call__(self, v):
        return v
    
a = A()
b = B()
check(a)
check(b)
print
print callable(a)
print callable(b)
callable
not callable

True
False

참고 문헌: 파이썬(열혈강의)(개정판 VER.2), 이강성, FreeLec, 2005년 8월 29일