52069


今、 mypy を用いて開発を行なっています。

`Iterable[Optional[type]]` があったとき、これを `Iterable[type]`
に変換する関数を記述しようと思い、次のコードを記述しました。

[code]

    from typing import Optional, Iterable
    
    
    def remove_none(iterable: Iterable[Optional[int]]) -> Iterable[int]:
        return filter(lambda x: x is not None, iterable)
    
[/code]

しかし、これは以下のエラーになります。

[code]

    test.py:5: error: Argument 2 to "filter" has incompatible type "Iterable[Optional[int]]"; expected "Iterable[int]"
    
[/code]

### 質問

Iterable の中身から Optional を外すのは、割と一般的な行為だと思われますが、これは、 mypy
でエラーにならないで実装するのは、一般的にどのように行われますか？


CC BY-SA 4.0
2019-01-18T03:34:57.047
52067
2019-01-22T13:47:40.270
754
question
python python3 mypy
mypy で Iterable[Optional[type]] から Iterable[type] を導出したい


## 地道な方法

地道に下の通り書くと型チェックを通りました。

[code]

    def remove_none_iterator(it: Iterator[Optional[int]]) -> Iterator[int]:
        while True:
            try:
                elem = next(it)
                while elem is None:
                    elem = next(it)
                yield elem
            except StopIteration:
                return
    
    def remove_none(iterable: Iterable[Optional[int]]) -> Iterable[int]:
        it = iter(iterable)
        return remove_none_iterator(it)
    
[/code]

ただしこれだと返り値が必ずイテレーターになってしまいます。しかしそもそも `filter()` 関数は iterable
を受け取ってイテレーターを返すので、質問文のプログラムとは同じ挙動になるはずです。

Iterable 全体に対応するためにはもっと地道にクラスごとに場合分けすれば良さそうですが、そもそも Iterable
全体に対応する必要があるのか疑問です。たとえばタプルも Iterable ですが、タプルから `None`
の要素を除いて別の長さのタプルにする操作はやや不自然に感じます。

## 無視する方法

このくらい自明な処理だと、`cast()` や `# type: ignore` を使って型チェックを無視させてしまうというのも手です。

[code]

    def remove_none(it: Iterable[Optional[int]]) -> Iterable[int]:
        return cast(Iterable[int], filter(lambda x: x is not None, it))
    
    # または
    
    def remove_none(it: Iterable[Optional[int]]) -> Iterable[int]:
        return filter(lambda x: x is not None, it)  # type: ignore  # https://ja.stackoverflow.com/q/52067
    
[/code]

前者は `remove_none()` の型と合うかはチェックされ、後者は単に無視されます。ただし前者も実行時のチェックはされません。

文字通り型チェックが無視されるので、人間のミスに気を付けなければいけません。PEP 484 "Type Hints"
には以下のように書かれているので、プロジェクトにとって良い塩梅を見極めるのが良さそうです。

> It should also be emphasized that **Python will remain a dynamically typed
> language, and the authors have no desire to ever make type hints mandatory,
> even by convention.**

## 誤った方法

型チェックに通ったからと言って完全に安心できるわけではありません。たとえば Python 組み込み関数 `filter()` は、フィルター用関数に
`None` を指定すると false-y な要素を除去する挙動をします。

> _function_ が `None` なら、恒等関数を仮定します。すなわち、 _iterable_ の偽である要素がすべて除去されます。

mypy においてもこの挙動を反映していて、フィルター用関数が `None` かそうでないかで `filter` の型を切り替えています。

  * `filter(None, it: Iterable[Optional[_T]]) -> Iterator[_T]`
  * `filter(f: Callable[[_T], Any], it: Iterable[_T]) -> Iterator[_T]`

型だけ見ると第一引数を `None` にすれば良さそうに見えますが、 **このとき`0` や `False` などもフィルタリングされてしまいます。**

[code]

    def remove_none(iterable: Iterable[Optional[int]]) -> Iterable[int]:
        return filter(None, iterable)
    
    list(remove_none([0, 1, 2, None]))  # ==> [1, 2]
    
[/code]

フィルター用関数が `None` でない場合は `Iterable[_T]` から `Iterator[_T]`
を作る形になるので、`Iterable[Optional[_T]]` を受け取っても `Iterable[Optional[_T]]`
しか返せません。関数型プログラミング言語でよく使われている `filter` 関数の型からすればこちらの挙動の方が自然で、型を変えるには `map`
を使う発想になるでしょう。


CC BY-SA 4.0
2019-01-18T05:31:25.843
52069
2019-01-22T13:47:40.270
2019-01-22T13:47:40.270
19110
19110
52067
answer