전문가를 위한 파이썬 1.0

파이썬 데이터 모델

데이터 모델이란?

언어 자체의 구성단위에 대한 인터페이스의 정의, 파이썬에서는 데이터 모델, 프로그래밍에서는 객체 모델이라 부른다.

특별 메서드의 개념

obj.__name__()와 같이 메소드 중 앞 뒤로 두개의 언더바를 가지는 메서드. 인터프리터가 다양한 문맥에서 호출한다.

예: 반복, 속성 접근(obj[]), 연산자 오버로딩, …

동의어: 마술 메서드, 던더(dunder, double under) 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class HelloWorld:
    def __init__(self):
        pass
    def __len__(self):
        return 2
    def __getitem__(self,pos):
        return ['Hello','World'][pos]

helloworld = HelloWorld()
print(len(helloworld)) # 2
print(helloworld[1]) # World
print(helloworld[-1]) # World

from random import choice

print(choice(helloworld)) # 'Hello' or 'World'

print(helloworld[1::-1]) # ['World','Hello']

for item in helloworld:
    print(item)
# Hello
# World

장점

  • 동작에 대한 임의 메서드명을 암기할 필요가 없다.
  • 쉽게 동작을 위임하여 다른 기능을 추가할 수 있다.
    • helloworld[1::-1](__getitem__)
    • for item in HelloWorld: ...(__len__,__getitem__)
  • 표준 라이브러리와 연계가 쉽다. 예: choice(helloworld)

한 특별 메서드의 기능이 다른 메서드에 위임되기도 한다.

예: __contains__메서드가 없으면 선형 탐색을 통해 찾아본다.

1
2
print('Hello' in helloworld) # True
print('Hi' in helloworld) # False

여기서 in 은 다음과 같은 선형 탐색을 한다.

1
2
3
4
for item in helloworld:
    if item == key:
        return True
return False

그러나 __init__를 제외한 특별 메서드는 직접 호출하지 않는것이 좋다. 내부적으로 특별 메서드 보다 빠른 방식이 있다면 빠른 방식을 택하기 떄문.

len(data)의 경우 내장 데이터 형의 경우 특별 메서드를 호출하지 않기도 하며, 이 경우 바이너리로 필드를 읽어 훨신 더 빠르다. 따라서 len과 같은 함수들은 메서드가 아니라 함수여야한다.

결론

  • 특별 메서드를 지원하여 다양하게 활용할 수 있도록 하자
  • 던더 메서드는(__f__) 특별 메서드이므로 일반 함수에서는 저런 꼴의 이름을 작성하지 말자.
  • 특별 메서드는 간접적으로 호출하자

개인 메모

  • 특별 메서드를 지원하면 가능해지는 연산을 꼭 확인하자. 의도치 않게 위임을 통해 지원하게 되면, O(1)로 가능한 연산도 O(n)으로 지원하게 되어 구현을 깜빡할 수 있다.

개인적으로 정리

이항 산술 연산자

표현식 특별 메서드
a+b a.__add__(b)
a-b a.__sub__(b)
a*b a.__mul__(b)
a/b a.__truediv__(b)
a//b a.__floordiv__(b)
a%b a.__mod__(b)
a**b a.__pow__(b)
a<<b a.__lshift__(b)
a>>b a.__rshift__(b)
a&b a.__and__(b)
a^b a.__xor__(b)
a|b a.__or__(b)

앞에 r을 붙이면 a,b가 바뀐다

예: a+b에서 a.__add__가 없을 경우 b.__radd__(a)

앞에 i를 붙이면 산술 대입 연산자가 된다

예: a+=b 에서 a.__iadd__(b)


단항 산술 연산자

표현식 특별 메서드
-a a.__neg__()
+a a.__pos__()
~a a.__invert__()

f(a)a.__f__인 예

divmod, pow, abs, complex, int, float, round, trunc, floor, ceil, hash, len, length_hint, repr, ...


접근 연산자

표현식 특별 메서드
a[b:c:] a.__getitem__(slice(b,c,None))
a[b] a.__getitem__(b)
a[b] = c a.__setitem__(b,c)

호출 연산자

표현식 특별 메서드
a(b,c,...) a.__call__(b,c,...)

문자열화 연산자

표현식 특별 메서드
print(a) print(str(a))
str.format(a,...) str.format(str(a)),...)
a.__repr__() '<a에 대한 설명>' 혹은 '같은 a를 생성할 수 있는 표현식'

위임 되는 예

표현식 특별 메서드
reversed(a) __reversed__, 없으면 __len__, __getitem__
b in a __contains__, 없으면 __iter__, 없으면 __getitem__
bool(a) __bool__, 없으면 __len__() != 0, 없으면 return True
str(a) __str__, 없으면 __repr__()

복합 할당의 경우(본문 2.6)

1
a[i] += b

는 다음과 같다

1
a.__setitem__(i, a.__getitem__(i).__iadd__(b))
  • 복합 할당일때는 원자적인 연산이 아니다.
  • a가 불변 시퀀스일 경우, __iadd__의 실행 후 __setitem__이 호출되므로, iadd는 성공했으나 setitem에서 오류가 발생한다. 이때 a의 엘리먼트는 iadd되었으므로 예외는 발생했으나 add는 정상적인 요상한 상황을 만든다. 따라서 가변 항목을 불변 시퀀스에 넣는건 좋은 생각이 아니다.