파이썬 프로그래밍의 대부분은 데이터를 담은 클래스들을 정의하고 이 객체들이 연계되는 방법을 명시하는 일입니다. 모든 파이썬 클래스는 일종의 컨테이너로, 속성과 기능을 함께 캡슐화합니다. 파이썬은 데이터 관리용 내장 컨테이너 타입(리스트, 튜플, 세트, 딕셔너리)도 제공합니다.

시퀀스처럼 쓰임새가 간단한 클래스를 설계할 때는 파이썬의 내장 list 타입에서 상속받으려고 하는 게 당연합니다. 예를 들어 멤버의 빈도를 세는 메서드를 추가로 갖춘 커스텀 리스트 타입을 생성한다고 가정합니다.

class FrequencyList(list):
    def __init__(self, members):
        super().__init__(members)

    def frequency(self):
        counts = {}
        for item in self:
            counts.setdefault(item, 0)
            counts[item] += 1
        return counts


# list에서 상속받아 서브클래스를 만들어서 list의 표준 기능을 모두 갖춰 파이썬 프로그래머에게 시맨틱을 유지
# 또한 추가한 메서드로 필요한 커스텀 동작을 더할 수 있음


foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd'])
print('Length is ', len(foo))
foo.pop()
print('After pop: ', repr(foo))
print('Frequency: ', foo.frequency())

# 결과
# Length is  7
# After pop:  ['a', 'b', 'a', 'c', 'b', 'a']
# Frequency:  {'a': 3, 'b': 2, 'c': 1}


이제 list의 서브클래스는 아니지만 인덱스로 접근할 수 있게 해서  list처럼 보이는 객체를 제공한다고 합니다. 예를 들어 바이너리 트리 클래스에 (list나 tuple같은) 시퀀스 시맨틱을 제공한다고 가정합니다.

class BinaryNode:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right


# 이 클래스가 시퀀스 타입처럼 동작하기 위해 어떻게 해야 할까?
#  파이썬은 특별한 이름을 붙인 인스턴스 메서드로 컨테이너 동작을 구현

bar = [1, 2, 3]
bar[0]

# 위와 같이 시퀀스의 아이템을 인덱스로 접근하면 다음과 같이 해석
bar.__getitem__(0)


# BinaryNode클래스가 시퀀스처럼 동작하게 하려면 객체의 트리를 깊이 우선으로 탐색하는 __getitem__을 구현

class IndexableNode(BinaryNode):
    def _search(self, count, index):
        # ...
        # (found, count) 반환
        pass

    def __getitem__(self, index):
        found, _ = self._search(0, index)

        if not found:
            raise IndexError('Index out of range')

        return found.value

tree = IndexableNode(10,
                     left=IndexableNode(5,
                                        left=IndexableNode(2),
                                        right=IndexableNode(6,
                                                            right=IndexableNode(7)
                                                            )
                                        ),
                     right=IndexableNode(15, left=IndexableNode(11)))

# 트리탐색은 물론 list처럼 접근 가능

print(tree.left.right.right.value)
print(tree[0])


# 결과
7
2


문제는 __getitem__을 구현한 것만드로 기대하는 시퀀스 시맨틱을 모두 제공하지 못합니다.

len(tree)# 에러

이러한 문제처럼 count, index 메서드 등 프로그래머들이 사용하는 기본 메서드를 전부 정의하기는 생각보다 어렵습니다. 


이런 어려움을 피하기 위해 내장 collections.abc 모듈은 각 컨테이너 타입에 필요한 일반적인 메서드를 모두 제공하는 추상 기반 클래스들을 정의합니다. 이 추상 기반 클래스들에서 상속받아 서브클래스를 만들다가 깜빡 잊고 필수 메서드를 구현하지 않으면, 모듈이 에러를 뱉어냅니다.

from collections.abc import Sequence

class BadType(Sequence):
    pass

foo = BadType()

# 결과
# TypeError: Can't instantiate abstract class BadType with abstract methods __getitem__, __len__


앞에서 다룬 SequenceNode처럼 추상 기반 클래스가 요구하는 메서드를 모두 구혀ㅑㄴ하면 별도로 작업하지 않아도 클래스가 index와 count 같은 부가적인 메서드를 모두 제공합니다.

요약

쓰임새가 간단할 때는 list나 dict같은 파이썬의 컨테이너 타입에서 직접 상속

커스텀 컨테이너 타입을 올바르게 구현하는 데 필요한 많은 메서드에 주의

커스텀 컨테이너 타입이 collections.abc에 정의된 인터페이스에서 상속받게 만들어서 클래스가 필요한 인터페이스, 동작과 일치도록


+ Random Posts