2018/06/16

[Python] __getitem__ で想定外のインデックスが来たら必ず IndexError を返すべし

Python では __getitem__() を実装したクラスは Iterable になります。

参考

PEP 234 -- Iterators | Python.org

便利なのでよく使っていたのですが、for ループで Iterable に使用した場合、__getitem__() に想定外のインデックスが渡されることがあるので注意が必要です。

例えば、以下の例をみてください。

1
2
3
4
5
6
7
8
9
10
class MyIterable:
    def __len__(self):
        return 3
 
    def __getitem__(self, i):
        return str(i)
 
 
for i in MyIterable():
    print(i)

普通に考えると、0, 1, 2 と表示されて終わるように思います。 しかし、実際は無限ループに入ってしまいます。

PEP 234 をよく読むと、 Iteration の終了は IndexErrorStopIteration 等の Exception で判定されるとのこと1

つまり、以下のように修正する必要があります。

5
6
7
8
9
def __getitem__(self, i):
    if i < 0 or self.__len__() <= i:
        raise IndexError
 
    return str(i)

今回、mmap を使っていたのですが、mmap はアクセス違反をしても空のバイト列が返ってきます。 その際、そのまま空のバイト列を戻り値にしてしまうと、先のように無限ループに入ってしまうのです。

ちなみに、__getitem__() のような特殊メソッドに関しては以下の書籍に詳しく書かれています。お勧め。

  1. StopIteration__next__() の内部で使う想定と思われます 
?

0 件のコメント: