2018/06/16

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

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

> 参考
>
> [PEP 234 -- Iterators | Python.org](https://www.python.org/dev/peps/pep-0234/)



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

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

```python
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](https://www.python.org/dev/peps/pep-0234/) をよく読むと、
Iteration の終了は `IndexError` や `StopIteration` 等の Exception で判定されるとのこと`StopIteration` は `__next__()` の内部で使う想定と思われます。

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

```python
`first-line: 5;
    def __getitem__(self, i):
        if i < 0 or self.__len__() <= i:
            raise IndexError

        return str(i)
```

今回、[mmap](https://docs.python.jp/3/library/mmap.html) を使っていたのですが、`mmap` はアクセス違反をしても空のバイト列が返ってきます。
その際、そのまま空のバイト列を戻り値にしてしまうと、先のように無限ループに入ってしまうのです。


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

0 件のコメント: