【Python】MutableSequenceを継承してリスト風クラスを作成する
リストのように扱えるクラスを作成する際にはcollections.anc.MutableSequence
を継承すると、最低限必要なメソッドを漏れなく実装することができる。
MutableSequenceの特徴
抽象基底クラスとは
MutableSequenceはコレクションのcollections.abcで定義されている抽象基底クラスの一つ。
Javaなどのインターフェースに相当するようなもので、抽象規定クラス自体では具体的なメソッド実装はされておらず、継承先のクラスで具体的なメソッド実装をする。
実際にコードを見る方が早い。
MutableSequenceのコード
以下のコードの最後の方にMutableSequenceが定義されている。
https://github.com/python/cpython/blob/3.11/Lib/_collections_abc.py
MutableSequenceのコードは以下の通り。
1class MutableSequence(Sequence):
2 """All the operations on a read-write sequence.
3 Concrete subclasses must provide __new__ or __init__,
4 __getitem__, __setitem__, __delitem__, __len__, and insert().
5 """
6
7 __slots__ = ()
8
9 @abstractmethod
10 def __setitem__(self, index, value):
11 raise IndexError
12
13 @abstractmethod
14 def __delitem__(self, index):
15 raise IndexError
16
17 @abstractmethod
18 def insert(self, index, value):
19 'S.insert(index, value) -- insert value before index'
20 raise IndexError
21
22 def append(self, value):
23 'S.append(value) -- append value to the end of the sequence'
24 self.insert(len(self), value)
25
26 def clear(self):
27 'S.clear() -> None -- remove all items from S'
28 try:
29 while True:
30 self.pop()
31 except IndexError:
32 pass
33
34 def reverse(self):
35 'S.reverse() -- reverse *IN PLACE*'
36 n = len(self)
37 for i in range(n//2):
38 self[i], self[n-i-1] = self[n-i-1], self[i]
39
40 def extend(self, values):
41 'S.extend(iterable) -- extend sequence by appending elements from the iterable'
42 if values is self:
43 values = list(values)
44 for v in values:
45 self.append(v)
46
47 def pop(self, index=-1):
48 '''S.pop([index]) -> item -- remove and return item at index (default last).
49 Raise IndexError if list is empty or index is out of range.
50 '''
51 v = self[index]
52 del self[index]
53 return v
54
55 def remove(self, value):
56 '''S.remove(value) -- remove first occurrence of value.
57 Raise ValueError if the value is not present.
58 '''
59 del self[self.index(value)]
60
61 def __iadd__(self, values):
62 self.extend(values)
63 return self
__setitem__
, __delitem__
, insert
がabstractmethodとして定義されているので、継承先で実装する必要がある。
なおMutableSequenceの継承元であるSequenceでは__getitem__
が、更にその何段階か先の継承元であるSizedで__len__
がabstractmethodとして定義されたままなので、定義する必要がある。
抽象基底クラスで実装が必要なメソッド
以下のページで各抽象基底クラスで実装が必要なメソッドが記載されている。
https://docs.python.org/ja/3/library/collections.abc.html#collections-abstract-base-classes
MutableSequenceはappend, reverse, extend, pop, remove, __iadd__が実装されていることが分かる(実際、ソースコードにも処理が書かれている)。
また、__getitem__, __setitem__, __delitem__, __len__, insertの実装が必要なことも記載されている。(これらのメソッドを実装せずに、インスタンス生成するとエラーになる。)
MutableSequenceを継承したクラス作成
必要なメソッドを実装しない場合はインスタンス生成時にエラー
MutableSequenceを継承して、何も実装しない場合、インスタンス生成時にエラーが発生する。
1from collections.abc import MutableSequence
2# 何も定義しない場合、インスタンス生成時にエラーとなる。最低限必要なメソッドを定義する必要がある
3class MyList(MutableSequence):
4 pass
5
6my_list = MyList()
7# TypeError: Can't instantiate abstract class MyList with abstract methods
8# __delitem__, __getitem__, __len__, __setitem__, insert
実装が必要なabstractmethodはIDEが教えてくれるが、実装し忘れたとしてもインスタンス生成時までエラーが発生しないので、注意が必要。
MutableSequenceを継承して必要なメソッドを実装
__delitem__, __getitem__, __setitem__, __len__, insertを実装。
また、__init__でself.dataを設定しないと値を持たせることができないため、実装する。
print文で内容を確認できるようにするために__repr__も実装しておく。
1from collections.abc import MutableSequence
2# __delitem__, __getitem__, __len__, __setitem__, insertを定義
3class MyList(MutableSequence):
4 # __init__がないとself.dataがないため、エラーとなる
5 def __init__(self, initlist=None):
6 self.data = []
7 if initlist is not None:
8 # XXX should this accept an arbitrary sequence?
9 if type(initlist) == type(self.data):
10 self.data[:] = initlist
11 elif isinstance(initlist, UserList):
12 self.data[:] = initlist.data[:]
13 else:
14 self.data = list(initlist)
15
16 def __getitem__(self, i):
17 if isinstance(i, slice):
18 return self.__class__(self.data[i])
19 else:
20 return self.data[i]
21
22 def __delitem__(self, i):
23 del self.data[i]
24
25 def __len__(self):
26 return len(self.data)
27
28 def __setitem__(self, i, item):
29 self.data[i] = item
30
31 def insert(self, i, item):
32 self.data.insert(i, item)
33 # print()でリストの内容を確認するために__repr__を作る
34 def __repr__(self):
35 return repr(self.data)
なお、コードはcollections.UserListのコードを参考に実装。(というかほぼそのまま)
https://github.com/python/cpython/blob/3.11/Lib/collections/init.py
MyListの動作確認
実装したメソッドは通常のlistのメソッドのように使える。
実装したabstractmethodの動作
1my_list = MyList([1, 2, 3])
2print(my_list)
3# __del__
4del my_list[2]
5print(my_list)
6# [1, 2]
7# __setitem__
8my_list[-1] = 4
9print(my_list)
10# [1, 4]
11# insert
12my_list.insert(0, 5)
13print(my_list)
14# [5, 1, 4]
15# __len__
16print(len(my_list))
17# 3
18# __getitem__
19print(my_list[0])
20# 5
21print(my_list[1:])
22# [1, 4]
MutableSequenceで予め実装されているメソッド
1# 予め定義されているメソッドも使用可能
2my_list = MyList([1, 2, 3])
3# append
4my_list.append(4)
5print(my_list)
6# [1, 2, 3, 4]
7# extend
8my_list.extend([5])
9print(my_list)
10# [1, 2, 3, 4, 5]
11# __iadd__
12my_list += [6]
13print(my_list)
14# [1, 2, 3, 4, 5, 6]
15my_list.pop()
16print(my_list)
17# [1, 2, 3, 4, 5]
18my_list.remove(5)
19print(my_list)
20# [1, 2, 3, 4]
21my_list.reverse()
22print(my_list)
23# [4, 3, 2, 1]
上記以外にもin, index, countなど、MutableSequenceの継承元で実装されているメソッドも使える。
まとめ
MutableSequenceを継承する場合は、__delitem__, __getitem__, __setitem__, __len__, insertを実装する。
1from collections.abc import MutableSequence
2# __delitem__, __getitem__, __len__, __setitem__, insertを定義
3class MyList(MutableSequence):
4 # __init__がないとself.dataがないため、エラーとなる
5 def __init__(self, initlist=None):
6 self.data = []
7 if initlist is not None:
8 # XXX should this accept an arbitrary sequence?
9 if type(initlist) == type(self.data):
10 self.data[:] = initlist
11 elif isinstance(initlist, UserList):
12 self.data[:] = initlist.data[:]
13 else:
14 self.data = list(initlist)
15
16 def __getitem__(self, i):
17 if isinstance(i, slice):
18 return self.__class__(self.data[i])
19 else:
20 return self.data[i]
21
22 def __delitem__(self, i):
23 del self.data[i]
24
25 def __len__(self):
26 return len(self.data)
27
28 def __setitem__(self, i, item):
29 self.data[i] = item
30
31 def insert(self, i, item):
32 self.data.insert(i, item)
実際に、MutableSequenceを継承したい場合は、自前のクラスを配列のように扱いたい場合が多いと思うが、その場合は、__init__やappendなどで、値設定に制約を入れたり、扱いたいクラスに合わせて比較や取得系のメソッドを実装していくことになる。