読んで欲しい人
- python初学者でシーケンス(listやtupleなんか)、条件分岐ぐらいまで終わった人
- 超初級を脱出したい(自分も同様w)
- __iter__って何?iterableとiteratorって何が違うのって人?
連載項目
- iteratorの理解を深める
- generatorの理解を深める
- decoratorの理解を深める
- 特殊メソッドに慣れる
- ポリモーフィズムに慣れる
目標は一旦終わった段階で以下のようなステータスになっていることです。
・pythonのチュートリアルが終わった感じのレベル ・iterater/generatorといった生成機っぽいやつにパッとみの辛さを感じない ・printしたらobj instanceが返ってくるという悲しい展開からの脱却 ・特殊メソッドやプライベート変数とうに少し慣れる(呼び出しの概念が少し理解できた感じ) ・ポリモーフィズムを自分で作れる感じ(ただし、非常に簡単なやつ)
generatorを理解すると何ができる?今回の記事のゴール
今回の記事も、pythonのgeneratorにおいて何が起きているのか、単純な関数との違いを理解することがメインなので、特に何ができるようになるわけではないかと思います。
一応最後に私がよく使うchunk generatorも紹介してみます。
(おさらい)そもそも関係性は?
前回の記事でも書きましたが、ざっくりいうと以下の関係性になっています。

今回はgeneratorなので、iteratorの一つをの挙動をみていくことになります。iterator自体の内容については、前回記事をご確認ください。真面目に全て説明すると、1記事では到底終わりませんので、前回同様端折ってシンプルにいきます。
generatorってiteratorのなかで、何が特別なの?
コード外観
generatorって名前だけ出されても実際のコードをみてみないと、何も始まらないので、簡単なgeneratorをみていきましょう。
def sample_generator():
yield 1
yield 2
yield 3
ge = sample_generator()
print(dir(ge))
print(next(ge))
print(next(ge))
print(next(ge))
print(next(ge))
def部分がgenerator関数です。これまでみてきた関数と何か変わってると思いませんか?
そうです、いつもreturnを利用していた戻り値の部分が、yieldに変わっています。コードの見た目上の変化はここだけです。
構成要素確認
では、実行してみましょう。最初にdirで構成要素を確認していきます。表示結果は以下になると思います。
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
いつものようにいろいろと出てきますが、本当にイテレータなのか確認してみましょう。
イテレータであるということは以下の2つの要素を含んでいるはずでしたよね?
- __iter__を含む(iterableである)
- __next__を含む(順々に要素を取り出せる)
両方ともの要素が含まれていることが確認できたかと思います。
通常関数との違い
では、next(ge)として実行していきましょう。面白い結果が出てきます。
1 2 3 Traceback (most recent call last): File "test4.py", line 11, in print(next(ge)) StopIteration
数字が1→2→3と表示され、その後にstopIterationが生じていることが確認できますね。まずは、1→2→3の部分は後に回して、stopIterationからおさらいしましょう。
これは、前回のiteratorでも出てきた、上限値を超えた場合に生じる戻り値です。ということは、この関数は上限3までの何かを返すiteratorであると言えます。
ステートを保有するgenerator
次に1→2→3の部分ですが、これはprint(next(ge))を3回呼び出した結果です。returnと何が違うかわかりますでしょうか?
returnの場合、特定の処理結果を返しますよね。すごくシンプルな関数を使って思い出してみます。
def sample():
return 1
sa = sample()
print(sa)
print(sa)
print(sa)
何が表示されるでしょうか?当たり前ながら1が3回表示されます。returnを1,2,3としたらいいんじゃないかと思う人もいるかもしれませんが、その場合は(1,2,3)を3回返すだけです。
これが、generatorと通常の関数の大きな違いで、generatorはステート(状態)を保持するという特徴です。つまり、1回呼び出されたら、そのgeneratorは今1回呼び出されているから、次は2回目だな、と勝手に考えてくれるということです。
個人的によく使うgenerator
最後に自分がよく使うgeneratorを紹介していきます。generatorの特徴として先ほどあげたステート保持という特徴は、そもそもどんなケースで有用でしょうか?個人的には以下のケースが使いやすいかなと思っています。
- 再帰処理
- パイプラインを構築する場合(パイプラインの説明は省略しますので、参考リンクを貼っておきます https://brett.is/writing/about/generator-pipelines-in-python/)
- 字句解析関係
個人的には字句解析関係のライトなもので使っています。と言っても、私自身がgeneratorを厳格に理解して、有効活用できているという感じではないんですが。。あくまで私レベルが使う非常に簡単なサンプルが以下のchunk区切りをするときのものです。
def chunk(target, size):
while target:
yield target[:size]
target = target[size:]
これに以下みたいなサンプルを食わせると、こんな処理結果になります、というのも合わせて。
target = "aaabbbcccdddeeeffff"
size = 3
ch = chunk(target, size)
for i in ch:
print(i)
aaa bbb ccc ddd eee fff f
綺麗に3文字ずつに区切って表示してくれています。通常の関数だともう少しめんどくさいですよね。。
まとめ
さて、超初級脱出連載2回目はgeneratorについて取り上げてみました。generatorはiteratorの一部ですが、ステートを保持するという特徴を有しています。ちなみに、このステートという言葉、今後いつか紹介したいと思っているAPIのRESTでもよく出るフレーズですので、慣れておいていただけるとありがたいです。
次は、decoratorです。要は装飾の仕方なのですが、実際のweb appsを作成するときによく利用するフレームワーク(pythonの場合、django/flask、 rubyだとRoR, phpだとLaravel/cakePHP)でよく利用しますので、ぜひ慣れていきたいなと思います。
連載目次: python超初級脱出シリーズ
pythonの入門編を終えた後に、supper_beginnerクラスを脱出するためのシリーズです。unitテストやフレームワークの使い方に行く前に、少しpythonの知識を深めたい人用の記事です。