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




1 연산자 중복 (Operator Overloading)


1-1 수치 연산자 중복

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

버전

메소드(Method)

연산자(Operator)

인스턴스 o에 대한 사용 예

Python2.x/3.x

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

+ (이항)

o + B, o += B

Python2.x/3.x

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

- (이항)

o - B, o -= B

Python2.x/3.x

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

\*

o \* B, o \*= B

Python2.x

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

/

o / B, o /= B

Python3.x

\_\_truediv\_\_(self, B)

/

o / B, o /= B

Python2.x

\_\_rdiv\_\_(self, B)

/

B / o

Python3.x

\_\_rtruediv\_\_(self, B)

/

B / o

Python2.x/3.x

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

//

o // B, o //= B

Python2.x/3.x

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

%

o % B, o %= B

Python2.x/3.x

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

divmod()

divmod(o, B)

Python2.x/3.x

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

pow(), \*\*

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

Python2.x/3.x

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

<<

o << B, o <<= B

Python2.x/3.x

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

>>

o >> B, o >>= B

Python2.x/3.x

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

&

o & B, o &= B

Python2.x/3.x

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

^

o ^ B, o ^= B

Python2.x/3.x

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

|

o | B, o |= B

Python2.x/3.x

\_\_neg\_\_(self)

- (단항)

-A

Python2.x/3.x

\_\_abs\_\_(self)

abs()

abs(o)

Python2.x/3.x

\_\_pos\_\_(self)

+ (단항)

+o

Python2.x/3.x

\_\_invert\_\_(self)

~

~o

In [3]:
class MyInteger:
    def __init__(self, i):
        self.i = i
        
    def __str__(self):
        return str(self.i)

    def __add__(self, other):
        return self.i + other

    def __sub__(self, other):
        return self.i - other

    def __mul__(self, other):
        return self.i * other


i = MyInteger(10)
print(i)
print(str(i))

print()
i = i + 10
print(i)

print()
i += 10
print(i)

print()
i += 15
print(i)

print()
i *= 10
print(i)
10
10

20

30

45

450
  • [주의] 위 코드는 연산자 오버로딩을 올바로 설명하지 못하는 코드. 아래 코드가 올바른 코드임. 이유는?
In [5]:
class MyInteger2:
    def __init__(self, i):
        self.i = i
        
    def __str__(self):
        return str(self.i)

    def __add__(self, other):
        return MyInteger2(self.i + other)

    def __sub__(self, other):
        return MyInteger2(self.i - other)

    def __mul__(self, other):
        return MyInteger2(self.i * other)


i = MyInteger2(10)
print(i)
print(str(i))

print()
i = i + 10
print(i)

print()
i += 10
print(i)

print()
i += 15
print(i)

print()
i *= 10
print(i)
10
10

20

30

45

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

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

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

['abcd', 'abcd', 'abcd']
In [8]:
#python2.x
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__("_"))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-94c503aee3ff> in <module>()
      8 
      9 m = MyString("abcd_abcd_abcd")
---> 10 print(m / "_")
     11 print(m / "_a")
     12 

TypeError: unsupported operand type(s) for /: 'MyString' and 'str'
  • 연산자 왼쪽에 피연산자, 연산자 오른쪽에 객체가 오는 경우
    • 메소드 이름 앞에 r이 추가된 메소드 정의
In [6]:
#python3.x
class MyString:
    def __init__(self, str):
        self.str = str
        
    def __truediv__(self, sep):
        return str.split(self.str, sep)

    __rtruediv__ = __truediv__

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 [9]:
class MyString:
    def __init__(self, str):
        self.str = str
        
    def __neg__(self):
        t = list(self.str)
        t.reverse()
        return ''.join(t)

    __invert__ = __neg__
    
m = MyString("abcdef")
print(-m)
print(~m)
fedcba
fedcba
In [10]:
class MyString:
    def __init__(self, str):
        self.str = str
        
    def __floordiv__(self, sep):  
        return self.str.split(sep)[0]

    def __mod__(self, sep):
        return self.str.split(sep)[1]

    def __divmod__(self, sep):
        seperated_list = self.str.split(sep)
        return seperated_list[0], seperated_list[-1] 
    
m = MyString("aaaa_bbbb_cccc")
print(m // "_")
print(m % "_")
print(divmod(m, "_"))

print()
print(m.__floordiv__("_"))
print(m.__mod__("_"))
print(m.__divmod__("_"))
aaaa
bbbb
('aaaa', 'cccc')

aaaa
bbbb
('aaaa', 'cccc')

1-2 비교 연산자 중복

  • 각각의 비교 연산에 대응되는 메소드 이름이 정해져 있지만 그러한 메소드가 별도로 정의되어 있지 않으면 cmp가 호출됨
메소드 연산자 비고
__cmp__(self, other) 아래 메소드가 부재한 상황에 호출되는 메소드 python3.x 에서는 지원하지 않음
__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
In [11]:
#python3.x
class MyInteger:
    def __init__(self, i):
        self.i = i
        
    def __gt__(self, y):
        return self.i > y

    def __ge__(self, y):
        return self.i >= y
    
    def __lt__(self, y):
        return self.i < y
    
    def __le__(self, y):
        return self.i <= y        
    
    def __eq__(self, y):
        return self.i == y

    def __ne__(self, y):
        return self.i != y    
    
c = MyInteger(10)
print(c > 1)
print(c >= 1)
print(c < 1) 
print(c <= 1)
print()
print(c == 1) 
print(c != 1)
True
True
False
False

False
True
In [14]:
#python3.x
class MyInteger:
    def __init__(self, i):
        self.i = i

    def __gt__(self, y):
        if isinstance(y, MyInteger):
            return self.i > y.i
        elif isinstance(y, int):
            return self.i > y
        else:
            return False
            
    def __ge__(self, y):
        if isinstance(y, MyInteger):
            return self.i >= y.i
        elif isinstance(y, int):
            return self.i >= y
        else:
            return False
        
    def __lt__(self, y):
        if isinstance(y, MyInteger):
            return self.i < y.i
        elif isinstance(y, int):
            return self.i < y
        else:
            return False
        
    def __le__(self, y):
        if isinstance(y, MyInteger):
            return self.i <= y.i
        elif isinstance(y, int):
            return self.i <= y
        else:
            return False
        
    def __eq__(self, y):
        if isinstance(y, MyInteger):
            return self.i == y.i
        elif isinstance(y, int):
            return self.i == y
        else:
            return False
        
    def __ne__(self, y):
        if isinstance(y, MyInteger):
            return self.i != y.i
        elif isinstance(y, int):
            return self.i != y
        else:
            return False
    
c = MyInteger(10)
d = MyInteger(100)
e = 50

print(c > d)
print(c >= d)
print()

print(c > e)
print(c >= e)
print()

print(c < d) 
print(c <= d)
print()

print(c < e) 
print(c <= e)
print()

print(c == d) 
print(c != d)
print()

print(c == e) 
print(c != e)
False
False

False
False

True
True

True
True

False
True

False
True

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


  • 클래스를 개발할 때 다음 메소드들을 적절하게 구현하면 자신만의 시퀀스 자료형을 만들 수 있음
  • 변경불가능한 (Immutable) 시퀀스 자료형 및 매핑 자료형을 위해 구현이 필요한 메소드
메소드 연산자
__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 [19]:
#python3.x
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("Out of Index - " + str(k))
        return k * k
    
s1 = Square(10)
print(len(s1)) # s1.__len__()
print()
print(s1[0]) #s1.__getitem__(0)
print(s1[1]) #s1.__getitem__(1)
print(s1[4]) #s1.__getitem__(4)
print(s1[9]) #s1.__getitem__(9)
print(s1[20])
10

0
1
16
81
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-19-40a212cf3605> in <module>()
     19 print(s1[4]) #s1.__getitem__(4)
     20 print(s1[9]) #s1.__getitem__(9)
---> 21 print(s1[20])

<ipython-input-19-40a212cf3605> in __getitem__(self, k)
      9     def __getitem__(self, k):
     10         if k < 0 or self.end <= k:
---> 11             raise IndexError("Out of Index - " + str(k))
     12         return k * k
     13 

IndexError: Out of Index - 20
  • 다음 for 문은 s1에 대해 __getitem()__ 메소드를 0부터 호출하여 IndexError가 발생하면 루프를 중단한다.
In [22]:
s1 = Square(10)
for x in s1:
    print(x, end=" ")
0 1 4 9 16 25 36 49 64 81 
In [23]:
s2 = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
for x in s2:
    #print(x,)
    print(x, end=" ")
0 1 4 9 16 25 36 49 64 81 
  • __getitem__() 메소드가 정의되어 있다면 다른 시퀀스 자료형으로 변환이 가능
In [24]:
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 [26]:
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 object at 0x10d6553c8>
light
darkness
2
In [28]:
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 list(self.d.keys())

    def values(self):
        return list(self.d.values())

    def items(self):
        return list(self.d.items())
    
m = MyDict({'one':1, 'two':2, 'three':3})
print(m.keys())
print(m.values())
print(m.items())
['one', 'two', 'three']
[1, 2, 3]
[('one', 1), ('two', 2), ('three', 3)]

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


3-1 문자열로 변환하기

1) __repr__

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

2) __str__

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

s = StringRepr()
print(s)
print(str(s))
print(repr(s))
str called
str called
<__main__.StringRepr object at 0x10d658470>
In [34]:
eval('10 + 20')
Out[34]:
30
In [35]:
a = '10 + 20'
eval(a)
Out[35]:
30
In [37]:
b = "print('abc')"
eval(b)
abc
In [39]:
class StringRepr:
    def __init__(self, i = 10):
        self.i = i
        
    def __repr__(self):
        return 'StringRepr(100)'

s = StringRepr()
q = eval(repr(s))
print(q.i)
100

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

  • 클래스 인스턴스에 __call__ 메소드가 구현되어 있다면 해당 인스턴스는 함수와 같이 호출될 수 있다.
    • 인스턴스 x에 대해 x(a1, a2, a3)와 같이 호출된다면 x.__call__(a1, a2, a3)가 호출된다.
In [40]:
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 [41]:
class A:
    def __call__(self, v):
        return v
    
class B:
    def func(self, v):
        return v
    
def check(func):
    if callable(func):
        print('callable')
    else:
        print('not callable')
        
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일