여기 Python list가 하나 있다.

array = list(range(10000))


Python을 아는 사람이라면, array의 길이를 얻는 방법은 다음과 같이 간단하다는 것을 알 것이다.

length_or_array = len(array)  # == 10000


C++이나 Java 등의 다른 객체형 언어를 먼저 알고 있었다면 이런 의문이 들 수 있다.

"array.size 또는 array.length 같이 안 하고 왜 len이라는 함수를 쓰지?"


질문에 대한 답을 하기 전에 먼저 코드 몇 뭉치를 보고 오는 게 더 좋을 것 같다.


1. len__len__을 호출한다.

class ClassWithLen:
    def __init__(self, *items):
        self.storage = list(items)

    def __len__(self):
        return len(self.storage)

>>> obj = ClassWithLen(1, 2, 3, 4)
>>> len(obj)
4

너무 뻔한가? 하지만 lenobject.__len__ 과 같다고 생각하면 안된다.

class ClassWithLen:
    def __len__(self):
        return 'Really lengthy length!'

>>> obj = ClassWithLen(1, 2, 3, 4)
>>> obj.__len__()
'Really lengthy length!'
>>> len(obj)
TypeError: 'str' object cannot be interpreted as an integer

Python이 제공하는 len 함수는 class instance의 __len__ method가 만들어져 있는지부터 체크하고, 그 return 유형과 값이 알맞는 지까지 검사해서 최종적으로 0 이상의 정수 값을 주게 된다.


2. __iter__ 없이 iteration이 돌아갈 수 있다.

class ClassIterable:
    def __init__(self, *items):
        self.storage = list(items)

    def __getitem__(self, idx):
        return self.storage[idx]

>>> obj = ClassIterable(1, 2, 3)
>>> for item in obj: print(item)
1
2
3


Python이 어느 obj를 통해 for loop을 돌고자 할 때 다음과 같은 순서로 체크한다.

  1. __iter__가 제공되는지 체크하고, 있으면 이를 통해 iter(obj)를 사용하는 것으로 끝난다.
  2. 없다면 __getitem__이 있는지 체크하고, 있으면 index 0부터 시작해서 IndexError가 나올 때까지 __getitem__(index)를 호출해서 for loop을 제공한다.

이러한 일종의 규약 (protocol) 덕분에 __getitem__ 하나만 있으면 iterable type이 되고, 추가 코드 없이 아래와 같은 것들이 가능하다.

>>> obj[-1]  # negative index 가능
3
>>> obj[1:]  # slicing 가능
[2, 3]
>>> list(obj)  # iterable로 인자를 받는 모든 곳에 적용 가능
[1, 2, 3]
>>> [item for item in obj]  # listcomp, genexp 가능
[1, 2, 3]
>>> 1 in obj, 5 in obj  # __contains__ 없이 가능
(True, False)



Duck Typing

지금까지 써 본 예제를 통해 알 수 있는 Python data type의 특징이 바로 "Duck Typing"이다.

If it walks like a duck and it quacks like a duck, then it must be a duck.

"duck"이라는 이름에 구애받지 않고 "duck"이라는 종류에 맞는 행동을 하는 가를 구분의 기준으로 본다는 것이다. 그리고 Python은 그 "행동"을 magic method (또는 special method)라는 이름으로 칭하고 수 많은 행동을 정해놓고, 계속 개정되며 추가되고 있다.

대표적인 Python data type으로는 iterable, callable, awaitable... 또 뭐가 있더라, file-like object나 bytes-like object 등의 표현도 duck typing의 철학을 담고 있다고 볼 수 있다.

또한 Python에서 그 interface는 강제되지 않는다. 위의 __getitem__을 통한 iterable typing은 그 대표적인 예일텐데, 이와 같이 코드 레벨에서 강제되지 않고, 일종의 규약으로서 문서 레벨에서 제공되는 interface라고 이야기할 수 있다. 이를 OOP에서 protocol이라고도 부른다고 한다. Iterable type을 만족하기 위해 꼭 iterator class가 있어야만 하는 것도 아니고, __iter__가 마련되어 있어야만 하는 것이 아니다. 굉장히 유연한 data type model을 갖고 있는데, dynamic language로서의 특징을 살리고자 하는 BDFL의 설계라고 볼 수 있을 것이다.


Python의 type system에 대한 컨셉을 len 함수에서 볼 수 있었다.




참고 자료

Fluent Python, Luciano Ramalho, O'Reilly
강추하는 Python 책

관련 요약글 dgkim5360.github.io/blog/python/2017/07/duck-typing-vs-goose-typing-pythonic-interfaces/

wikipedia.org
Duck Typing wikipedia.org/wiki/Duck_typing
Protocol wikipedia.org/wiki/Protocol_(object-oriented_programming)

python.org
Data model - special methods docs.python.org/3/reference/datamodel.html#special-method-names
Glossary docs.python.org/3/glossary.html

  • 2018.02.12 00:23

    비밀댓글입니다