python超初級を脱出したい!③〜generatorの理解を深める〜

Python
この記事は約7分で読めます。
Advertisements

読んで欲しい人

  • python初学者でシーケンス(listやtupleなんか)、条件分岐ぐらいまで終わった人
  • 超初級を脱出したい(自分も同様w)
  • __iter__って何?iterableとiteratorって何が違うのって人?

連載項目

  1. iteratorの理解を深める
  2. generatorの理解を深める
  3. decoratorの理解を深める
  4. 特殊メソッドに慣れる
  5. ポリモーフィズムに慣れる

目標は一旦終わった段階で以下のようなステータスになっていることです。

・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の特徴として先ほどあげたステート保持という特徴は、そもそもどんなケースで有用でしょうか?個人的には以下のケースが使いやすいかなと思っています。

個人的には字句解析関係のライトなもので使っています。と言っても、私自身が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の知識を深めたい人用の記事です。

  1. python超初級を脱出したい!①〜超初級を脱出するには〜
  2. python超初級を脱出したい!②〜iteratorの理解を深める〜
  3. python超初級を脱出したい!③〜generatorの理解を深める〜
  4. python超初級を脱出したい!④〜decoratorの理解を深める〜
  5. python超初級を脱出したい!⑤〜特殊メソッド・特殊属性に慣れる〜

コメント