はじめに
ニュージェネレーションズ! nikkieです。
Python使いのみなさん、ジェネレータ使ってますか?
私は多用しています、もう手放せません!
そんなジェネレータ(を返す関数)の型ヒントについてアウトプットします。
目次
- はじめに
- 目次
- ジェネレータ関数って、何よ?
- 現時点の私の結論
- 動作環境
- typing.Generator
- collections.abc.Generator
- 別のやり方:IteratorやIterable
- なぜ3通りも書ける? 継承関係をもとに考える
- 終わりに
- P.S. その1 どうしてtypingの各種クラスが非推奨なんですか!
- P.S. その2 types.GeneratorType
ジェネレータ関数って、何よ?
端的に言えば、yield
を持った関数です。
def generator_function(): yield "👩<200d>🎨" yield "🐯" yield "🐟"
用語集を参照しましょう。
https://docs.python.org/ja/3/glossary.html#index-19
generator iterator を返す関数です。 通常の関数に似ていますが、 yield 式を持つ点で異なります。
ジェネレータ関数の返り値が ジェネレータイテレータ
https://docs.python.org/ja/3/glossary.html#term-generator-iterator
generator 関数で生成されるオブジェクトです。
>>> generator_iterator = generator_function() >>> next(generator_iterator) '👩\u200d🎨' >>> next(generator_iterator) '🐯' >>> next(generator_iterator) '🐟' >>> next(generator_iterator) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
ジェネレータには日々Pythonを書く中で助けてもらっていますが、ここでは詳細には立ち入りません1。
今回はジェネレータ関数の返り値(=ジェネレータイテレータ)をどう型ヒントするかを考えます。
現時点の私の結論
- ジェネレータ関数の返り値は
collections.abc.Generator
で型ヒントするのがいいのではないか collections.abc.Iterator
で型ヒントしてもよいかも- mypyのドキュメントに例示されている
[]
の指定の仕方がうるさくない
詳しく述べていきます。
動作環境
- Python 3.8.16
- mypy 1.2.0
typing.Generator
型ヒントをサポートするモジュールtyping
に、ジェネレータ関数の型ヒントに使う型Generator
があります。
https://docs.python.org/ja/3/library/typing.html#typing.Generator
ジェネレータはジェネリック型
Generator[YieldType, SendType, ReturnType]
によってアノテーションを付けられます。
先のジェネレータ関数の型ヒントは以下のようになります。
from typing import Generator def generator_function() -> Generator[str, None, None]: yield "👩<200d>🎨" yield "🐯" yield "🐟"
もしジェネレータが値を返すだけの場合は、
SendType
とReturnType
に None を設定してください:
collections.abc.Generator
typing.Generator
のドキュメントには、実は非推奨という記載があります。
バージョン 3.9 で非推奨: collections.abc.Generator now supports subscripting ([]).
Python 3.9からcollections.abc.Generator
が[]
をサポートするようになった2ので、typing.Generator
は非推奨とのことです。
私は作ったプログラムについて、継続してPythonのバージョンを上げていきたいと考えています。
今使っているPythonのバージョンが3.7や3.8だとしても、それは3.9, 3.10と上げたいです。
将来Python 3.9に上げたときにtyping.Generator
という型ヒントをすべて書き換えるのが面倒なので、今の私はcollections.abc.Generator
を選択しています。
https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Generator
ただしcollections.abc.Generator
はPython 3.8以前は[]
をサポートしていないので、合わせてfrom __future__ import annotations
も使います3。
from __future__ import annotations from collections.abc import Generator def generator_function() -> Generator[str, None, None]: yield "👩<200d>🎨" yield "🐯" yield "🐟"
別のやり方:Iterator
やIterable
mypyのドキュメントには、ジェネレータをtyping.Iterator
で型ヒントする例もあります4。
https://mypy-lang.org/
それにならうと、先の例は
from __future__ import annotations from collections.abc import Iterator def generator_function() -> Iterator[str]: yield "👩<200d>🎨" yield "🐯" yield "🐟"
ともできます。
typing.Generator
のドキュメントを再度確認すると
https://docs.python.org/ja/3/library/typing.html#typing.Generator
代わりに、ジェネレータを
Iterable[YieldType]
やIterator[YieldType]
という返り値の型でアノテーションをつけることもできます:
とあります。
typing.Iterable
5やtyping.Iterator
6もPython 3.9で非推奨になっているので、collections.abc
側を使っていきたい考えです。
なぜ3通りも書ける? 継承関係をもとに考える
ジェネレータ関数の返り値(ジェネレータイテレータ)に3通りも型ヒントが書ける理由を少し考えてみます。
https://docs.python.org/ja/3/library/collections.abc.html#collections-abstract-base-classes の継承関係を参照します7。
collections.abc.Generator
は「ジェネレータクラスの ABC」collections.abc.Iterator
は「__iter__()
メソッドと__next__()
メソッドを提供するクラスの ABC」collections.abc.Iterable
は「__iter__()
メソッドを提供するクラスの ABC」
「AがBを継承している」を「AはB」と表すと
よって、ジェネレータ関数の返り値はGenerator
でもIterator
でもIterable
でも型ヒントできると理解しました。
終わりに
ジェネレータ関数の返り値について、collections.abc.Generator[YieldType, SendType, ReturnType]
という意見を述べました。
ほかのやり方としてIterator
やIterable
を使う方法も紹介しています。
今回紹介したようなジェネリック型を使った型ヒントはtyping
のドキュメントが一番詳しい印象です。
初手でcollections.abc
のドキュメントを見に行っても、型ヒントを具体的にどう書けばいいかわからなくなる自信があります。
なので、オススメはtyping
のドキュメントを参照し、「非推奨」の記載があったらcollections.abc
を使うことです。
typing
のドキュメントの説明はcollections.abc
のジェネリック型についても成り立ちます!
P.S. その1 どうしてtypingの各種クラスが非推奨なんですか!
Python 3.9 リリース時点でtypingの諸々が非推奨になり、私は納得いかなかったのですが、それを題材にするLT駆動学習したところ納得しています。
型ヒントが受け入れられたから、CPython自体を変更できるようになった (スライド33)
コミュニティが型ヒントを受け入れたことを表す、非常に喜ばしい出来事なんですよ!
P.S. その2 types.GeneratorType
型ヒントとは別に、types.GeneratorType
というものがあります。
https://docs.python.org/ja/3/library/types.html#types.GeneratorType
これはジェネレータイテレータであることの検証に使う用途で知っていました。
ref: https://stackoverflow.com/a/12062004
import types assert isinstance(la, types.GeneratorType) # pytestだと思っています
- 詳細を知りたい方へのオススメは『Python実践入門』です。私の過去のアウトプットはこちら ↩
-
https://docs.python.org/ja/3/whatsnew/3.9.html#type-hinting-generics-in-standard-collections 総称型 (generics) として使用できる=
[]
をサポートということですね↩ - PythonのUnionまわりの型ヒントの書き方を整理する(Python 3.9と3.10が|が使える境目です) - nikkie-ftnextの日記でも取り上げました↩
- これを知ったのはPyCon JP 2019の「ListはIteratorですか?」がきっかけです。2020年のPython Charity Talksバージョンのスライドがこちらです:https://docs.google.com/presentation/d/1SonSdULukN88gWnMZLnMhyHcp3OcFRb5hvXZV6IYTPo/edit#slide=id.g62c3ed6e54_0_17↩
- https://docs.python.org/ja/3/library/typing.html#typing.Iterable↩
- https://docs.python.org/ja/3/library/typing.html#typing.Iterator↩
- Pythonのイテラブルとイテレータ 〜for文の秘密〜 - nikkie-ftnextの日記 でも採ったアプローチです↩