{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 43. 커스텀 컨테이너 타입은 collections.abc를 상속하라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "시퀀스처럼 사용법이 간단한 클래스를 정의할 떄는 파이썬 내장 리스트 타입의 하위 클래스를 만들고 싶은 것이 당연하다.\n", "\n", "예를 들어 멤버들의 빈도를 계산하는 메서드가 포함된 커스텀 리스트 타입이 필요하다고 가정하다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class FrequencyList(list):\n", " def __init__(self, members):\n", " super().__init__(members)\n", " \n", " def frequency(self):\n", " counts = {}\n", " for item in self:\n", " counts[item] = counts.get(item, 0) + 1\n", " return counts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "FrequencyList를 리스트의 하위 클래스로 만듦으로써 리스트가 제공하는 모든 표준 함수를 사용할 수 있다." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "길이: 7\n" ] } ], "source": [ "foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd'])\n", "print('길이: ', len(foo))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'d'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.pop()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"['a', 'b', 'a', 'c', 'b', 'a']\"" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "repr(foo)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'a': 3, 'b': 2, 'c': 1}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.frequency()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이제 리스트처럼 느껴지면서 인덱싱이 가능한 객체를 제공하고 싶은데, 리스트의 하위 클래스로 만들고 싶지는 않다고 ㄱ자ㅓㅇ해보자.\n", "\n", "예를 들어, 다음 이진 트리 클래스를 시퀀스(리스트나 튜플)의 의미 구조를 사용해 다룰 수 있는 클래스를 만들고 싶다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class BinaryNode:\n", " def __init__(self, value, left=None, right=None):\n", " self.value = value\n", " self.left = left\n", " self.right = right" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bar = [1, 2, 3]\n", "bar[0]" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bar.__getitem__(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "BinaryNode 클래스가 시퀀스처럼 작동하게 하려면 트리 노드를 깊이 우선 순회하는 커스텀 __getietm__ 메서드 구현을 제공하면 된다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [] }, "outputs": [], "source": [ "class IndexableNode(BinaryNode):\n", " def _traverse(self):\n", " if self.left is not None:\n", " yield from self.left._traverse()\n", " yield self\n", " if self.right is not None:\n", " yield from self.right._traverse()\n", "\n", " def __getitem__(self, index):\n", " for i, item in enumerate(self._traverse()):\n", " if i == index:\n", " return item.value\n", " raise IndexError(f'인덱스 범위 초과: {index}')" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "tree = IndexableNode(\n", " 10,\n", " left=IndexableNode(\n", " 5,\n", " left=IndexableNode(2),\n", " right=IndexableNode(\n", " 6,\n", " right=IndexableNode(7))),\n", " right=IndexableNode(\n", " 15,\n", " left=IndexableNode(11)))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " 10\n", " 5 15\n", "2 6 11\n", " 7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 트리를 left나 right 애트리뷰트를 사용해 순회할 수도 있지만, 추가로 리스트처럼 접근할 수도 있다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LRR: 7\n", "인덱스 0: 2\n", "인덱스 1: 5\n", "11이 트리 안에 있나? True\n", "17이 트리 안에 있나? False\n", "트리: [2, 5, 6, 7, 10, 11, 15]\n" ] } ], "source": [ "print('LRR:', tree.left.right.right.value)\n", "print('인덱스 0:', tree[0])\n", "print('인덱스 1:', tree[1])\n", "print('11이 트리 안에 있나?', 11 in tree)\n", "print('17이 트리 안에 있나?', 17 in tree)\n", "print('트리:', list(tree))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "문제는 getitem을 구현하는 것만으로는 리스트 인스턴스에서 기대할 수 있는 모든 시퀀스 의미 구조를 제공할 수 없다는데 있다." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "object of type 'IndexableNode' has no len()", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-12-dc0343ec22f7>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtree\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: object of type 'IndexableNode' has no len()" ] } ], "source": [ "len(tree)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "len 내장함수를 구현해주자\n", "\n", "커스텀 시퀀스 타입은 이 메서드를 꼭 구현해야한다" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class SequenceNode(IndexableNode):\n", " def __len__(self):\n", " for count, _ in enumerate(self._traverse(), 1):\n", " pass\n", " return count" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "tree = SequenceNode(\n", " 10,\n", " left=SequenceNode(\n", " 5,\n", " left=SequenceNode(2),\n", " right=SequenceNode(\n", " 6,\n", " right=SequenceNode(7))),\n", " right=SequenceNode(\n", " 15,\n", " left=SequenceNode(11))\n", ")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "트리 길이: 7\n" ] } ], "source": [ "print('트리 길이:', len(tree))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "안타깝지만 getitem과 len으로 시퀀스가 되지는 않는다.\n", "\n", "countㄴ타 index 메서드도 들어 있지 않다.\n", "\n", "이런 어려움을 덜어주기 위해 내장 collections.abc 모듈 안에는 컨테이너 타입에 정의해야하는 전형적인 메서드를 모두 제공하는 추상기반 클래스 정의가 여러가지 들어 있다.\n", "\n", "이런 추상 기반 클래스의 하위클래스를 만들고 필요한 메서드 구현을 잊어버리면, collections.abc 모듈이 실수한 부분을 알려준다." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "from collections.abc import Sequence\n", "\n", "class BadType(Sequence):\n", " pass" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Can't instantiate abstract class BadType with abstract methods __getitem__, __len__", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-18-fdca44b07998>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfoo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mBadType\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class BadType with abstract methods __getitem__, __len__" ] } ], "source": [ "foo = BadType()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "추상 기반 클래스가 요구하는 모든 메서드를 구현하면 index나 count와 같은 추가 메서드 구현을 거저 얻을 수 있다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "class BetterNode(SequenceNode, Sequence):\n", " pass" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "tree = BetterNode(\n", " 10,\n", " left=BetterNode(\n", " 5,\n", " left=BetterNode(2),\n", " right=BetterNode(\n", " 6,\n", " right=BetterNode(7))),\n", " right=BetterNode(\n", " 15,\n", " left=BetterNode(11))\n", ")" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7의 인덱스: 3\n", "10의 개수: 1\n" ] } ], "source": [ "print('7의 인덱스:', tree.index(7))\n", "print('10의 개수:', tree.count(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- 간편하게 사용할 경우에는 파이썬 컨테이너 타입(리스트나 딕셔너리 등)을 직접 상속하라.\n", "- 커스텀 컨테이너를 제대로 구현하려면 수많은 메서드를 구현해야 한다는 점에 주의하라\n", "- 커스텀 컨테이너 타입이 collection.abc에 정의된 인터페이스를 상속하면 커스텀 컨테이너 타입이 정상적으로 작동하기 위해 필요한 인터페이스와 기능을 제대로 구현하도록 보장할 수 있다." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }