이상한모임 Python 채널에서 이런 질문이 왔었습니다.

파이썬에서 dict 기본적으로 [key] 로 접근하는데 .key 로 접근하도록 변환하는 방법이 있을까요?

사실 이 질문의 답은 AttrDict라는 구현체가 있으므로 가져다 쓰는 것입니다. 하지만 직접 만드는 것도 전혀 어렵지 않기 때문에 간단히 구현해보고자 합니다.

Python에서 a라는 object에 대해서 a['key']와 같은 방식으로 ['key']를 사용하면 내부적으로는 a.__getitem__('key')과 같이 처리됩니다. a.key와 같은 방법으로 사용하면 내부적으로는 a.__getattr__('key')과 같이 처리됩니다. 즉, __***attr__이 호출되면 __***item__이 반환되게 하면 이 문제는 해결됩니다. 왜 ***이라고 적었느냐 하면, get외에도 setdel이 있기 때문입니다.1

간단하게 구현해보면 다음과 같습니다.

class AttrDict(dict):

    def __getattr__(self, attr):
        return self.__getitem__(attr)

    def __setattr__(self, attr, value):
        return self.__setitem__(attr, value)

    def __delattr__(self, attr):
        return self.__delitem__(attr)

실제로 테스트도 해보았습니다.

>>> from attrdict import AttrDict
>>> a = AttrDict()
>>> a['a'] = 1
>>> a.a
1
>>> a.a = 3
>>> a.a
3
>>> del a.a
>>> a['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'

위에 적었던 소스를 보면 알 수 있지만, 기본 자료형, 예를 들면 위에서 쓰인 dict등의 자료형은 그 자체로 이미 class이기때문에 상속받아서 활용하는 것이 가능합니다.

예를 들면 다음과 같은 소스를 봐보겠습니다.2

class BookList(object):
    list = []

    def add_book(self, title, author):
        self.list.append((title, author))

책 목록을 관리하는 class라고 만든 것인데, 개발 자체는 쉬울지 몰라도 실제로 활용하려고 하면 아래와 같은 문제들이 있을 것입니다.

>>> book_list = BookList()
>>> book_list.add_book('훈민정음', '세종대왕')
>>> book_list
????

????로 해놓았는데, 당연히 저 결과는 instance입니다. 즉, 실행할때마다 다른 값이 나올것이고, 우리가 원하는 book_list에 있는 실제 책 목록을 볼 수 없습니다. 물론 book_list.list라고 하면 되겠지만 book_list라고 하면 나오면 될 문제인데 뭔가 많이 돌아가는 느낌이 듭니다.

여기서 쓴 방법 대신 list 자료형을 상속받으면 다음과 같이 쓸 수 있습니다.

class BookList(list):

    def add_book(self, title, author):
        self.append((title, author))

이렇게 활용하면 장점이 몇 가지 있는데 꼽아보자면 다음과 같습니다.

  1. 정적 분석기가 자료형을 보다 잘 인식할 수 있습니다.
  2. 이미 기본으로 제공되는 메소드를 그대로 가져다 쓸 수 있습니다.
  3. type명만 보고도 그 동작을 알 수 있으므로 사용성이 증가됩니다.
  4. 1과 2로 인해 불필요한 라인 수와 중복, 그리고 버그를 줄일 수 있습니다다.

물론 상속이 만능은 아닙니다. 보다 복잡한 자료 구조가 필요하다면 직접 만들어야하는 경우도 존재할 것입니다. 하지만 간단하게 끝낼 수 있는 경우임에도 불구하고 복잡한 자료형을 만드는 것은 낭비 아닐까 생각해봅니다.


  1. 여기서 말한 get, set, del__get__, __set__, __delete__와는 아예 다른 것입니다. 전자는 그냥 prefix고, 후자는 디스크립터를 위한 메소드입니다.

  2. Python 2를 고려해서 object를 상속했습니다. Python 3이라면 안해도 됩니다.