Python

【Python】MutableSequenceを継承してリスト風クラスを作成する

MAX

リストのように扱えるクラスを作成する際には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

あわせて読みたい
【Python】カスタマイズしたlistクラスを作成する【UserList】
【Python】カスタマイズしたlistクラスを作成する【UserList】

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などで、値設定に制約を入れたり、扱いたいクラスに合わせて比較や取得系のメソッドを実装していくことになる。

スポンサーリンク
ABOUT ME
MAX
MAX
ITエンジニア、データサイエンティスト
新卒でSIerに入社し、フリーランスになってWEB系へ転向。
その後AIの世界へ足を踏み入れ、正社員に戻る。 テーブルデータの分析がメイン。
スポンサーリンク
記事URLをコピーしました