effective python#28
TRANSCRIPT
Effective Python読書会 #4
オズ @Wizard_of_Oz__
EffectivePython
項目 28カスタムコンテナ型は collections.abc を継承する
単純なユースケースの場合• List にメソッドを加えたい
– 要素の頻度を数えるメソッド
– 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 7After pop: [‘a’, ‘b’, ‘a’, ‘c’, ‘b’, ‘a’]Frequency: {‘a’ : 3, ‘c’ : 1, ‘b’ : 2}
継承しない場合• サブクラスではない形でセマンティクスを提供
– 二分木クラスにシーケンスのセマンティクスを提供
class BinaryNode(object): def __init__(self, value, left=None, right=None): self.value = value self.left = left self.right = right
シーケンスの振る舞いの実装• Python はコンテナの振る舞いを
特別な名前を持ったインスタンスメソッドで実装
– シーケンスに添字でアクセス
bar = [1, 2, 3]bar[0]
– Python の解釈
bar.__getitem__(0)
BinaryNode クラスをシーケンスのように振る舞わせる• __getitem__ の実装を提供する
– 二分木クラスにシーケンスのセマンティクスを提供
class IndexableNode(BinaryNode): def _search(self, count, index): .... # Returns (found, count)
def __getitem__(self, index): found, _ = self._search(0, index) if not found: raise IndexError('Index out of range') return found.value
BinaryNode クラスをシーケンスのように振る舞わせるclass IndexableNode(BinaryNode): def _search(self, count, index): found = None if self.left: found, count = self.left._search(count, index) if not found and count == index: found = self else: count += 1 if not found and self.right: found, count = self.right._search(count, index) return found, count # Returns (found, count)
def __getitem__(self, index): found, _ = self._search(0, index) if not found: raise IndexError('Index out of range') return found.value
BinaryNode クラスをシーケンスのように振る舞わせる• IndexableNode
– 通常の二分木として構築できる
– List のようにアクセスできる
print('LRR =', tree.left.right.right.value) #LRR = 7print('Index 0 =', tree[0]) #Index 0 = 2print('Index 1 =', tree[1]) #Index 1 = 5print('11 in the tree?', 11 in tree) #11 in the tree? Trueprint('17 in the tree?', 17 in tree) #17 in the tree? Falseprint('Tree is', list(tree)) #Tree is [2, 5, 6, 7, 10, 11, 15]
__getitem__ を実装した場合の問題点• シーケンスのセマンティクス全てを提供するには不十分
– __len__ という特別なメソッドが必要
len(tree)
>>>TypeError: object of type ‘IndexableNode’ has no len()
独自のコンテナ型を定義するのは困難• 他にも特別なメソッドの定義が必要
– count や index というメソッドが足りない
• collections.abc モジュール– 典型的なメソッドをすべて提供する抽象基底クラス
– 必要なメソッドの実装がないと教えてくれる
from collections.abc import Sequenceclass BadType(Sequence): pass
foo = BadType()>>>TypeError: Can’t instantiate abstract class BadType with abstract methods __getitem__, __len__
独自のコンテナ型を定義するのは困難try: from collections.abc import Sequence class BadType(Sequence): pass foo = BadType()except: logging.exception('Expected')else: assert False
>>>TypeError: Can’t instantiate abstract class BadType with abstract methods __getitem__, __len__
collections.abc• 抽象基底クラスに必要な全てのメソッドを実装
– 追加メソッドが提供される (index や count など )– 複雑な型 (Set や MutableMapping 等 ) で特に有用
– 実装が必要な特殊メソッドの個数が膨大
class BetterNode(SequenceNode, Sequence): pass
tree = BetterNode(...)
print('Index of 7 is', tree.index(7)) #Index of 7 is 3print('Count of 10 is', tree.count(10)) #Count of 10 is 1
まとめ• 単純なユースケースは Python のコンテナ型から直接継承
– コンテナ型: list や dict など
• カスタムコンテナ型を正しく実装するには多数のメソッドが必要
• カスタムコンテナ型は collections.abc で定義されたインタフェースを継承する– 必要なインタフェースを備えていることを確かなものにするため