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

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

読んで欲しい人

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

iteratorを理解すると何ができる?今回の記事のゴール

今回の記事は何ができるではなく、pythonのiterにおいて何が起きているのかを理解することがメインなので、特に何ができるようになるわけではないかと思います。

唯一できるようになることと言えば、自作のイテレータが作成できるようになることくらいでしょうか。

そもそも関係性は?

iteratorやgeneratorを考えていきますが、そもそもこれまでpythonの型とかを勉強してくるとシーケンスやコンテナ、その他いろんな用語学んできていると思います。

その関係性をまずはざくっとイメージしてみます。正直ラフ図なので厳格ではない、そして抜けている要素もありますが、そこらへんは理解するための概観イメージとしてお許しください。

概念イメージ

大きくiterable(イテラブル)な要素の中に、これまで学んできている組み込みの基本型(list/tuple/set/dictなど)も含まれています。

iterableとiteratorって何が違うの?

iterable、正確にはiterable objectとは何なんでしょうか?

シンプルに行ってしまうと、iteratorに変換できるオブジェクトをiterable object(イテラブル・オブジェクト)といいます。

他方、iteratorは要素を順番に取り出すことができるオブジェクトのことを指します。for文を作成するときにlistやtuple入れて実行していたと思いますが、listやタプル自体はイテレータではないのですが、イテラブルなので、イテレータ要素に変換されて自動で実行されていました。

・・・・・って、日本語として非常にわかりにくいですよね。ということで、実際にそれぞれの要素がもつdirを確認しながらみていきましょう。

listのdirをみてみよう

さて、以下のコードを実行してみましょう。変数aはリストになっており、aをdir(a)として表示することで、リストのdirが確認できます。

a = []
print("listのdir:",dir(a))

こんな感じで表示されていますよね?少しみにくいかと思いますが、ご容赦ください。

ここで注目するのは、__iter__という要素が含まれていることです。この__iter__要素を含んでいる物がiterable objectとなります。

では、__iter__って何をしているかというと、for文とかで呼び出されたときに、イテレータを自動生成してくれる要素です。つまりlist自体はイテレータではなくて、イテレータの要素を持ったiterableなオブジェクトっていうことになります。

list.__iter__のdirをみてみよう

じゃぁこの__iter__っていうのが、どういう要素を有しているのかもみていきましょう。

a = []
print("list.__iter__のdir:",dir(a.__iter__()))

こんな感じで表示されていますよね?少しみにくいかと思いますが、ご容赦ください。

これも同様に__iter__要素を保有していますね。ということで、当たり前ではあるのですが、iteratorのiterableなオブジェクトです。では、何が違うのでしょう。

ここで注目する違いは、__next__という要素を保有しているところです。これが要素を順番に取得するために必要な要素となっており、list自体にはなかったけど、list.__iter__は保有する要素です。

この要素を持つ物が、iteratorと呼ばれるものになります。

なんか回りくどい感じしない?と思ったあなたへ

なぜlist自体をiteratorとしないのでしょうか?そこを理解するためには、iteratorが全ての要素を実行しおえたあとの動作も勉強する必要があります。

まぁシンプルに言ってしまうと、イテレータってリミットまで処理したら、動作を止める処理が含まれていて、何度も再利用しようとしたときには別のオブジェクトを作る必要があります。

ってことは、リスト自体をイテレータにしてしまった場合、再利用が効かなくなるんですよね。よくわからない部分もあると思うので、実際に自作イテレータを作成してみましょう。

イテレータを自作してみよう

イテレータの実装はシンプルに作ると以下のようになります。実際のイテレータは”呼び出されたよ”とかはもちろん表示しませんが、呼び出されたことがわかるように、ここでは入れています。

class CountUpIterator:
    def __init__(self, limit=5):
        self.limit = limit
        self.counter = -1
    def __iter__(self):
        print('イテレータが呼び出されたよ')
        return self
    def __next__(self):
        print('nextが呼び出されたよ')
        self.counter += 1
        if self.counter > self.limit:
            raise StopIteration()
        return self.counter

どんな動作が予定されているかというと、一回呼び出されるたびにcounterを1ずつプラスしていくだけのイテレータです。実際に動かしてみましょう。

ci = CountUpIterator(10) #limitを10にしてイテレータを初期化
print(ci.limit) #初期化した10が表示される
print(ci.counter) #初期値の-1が表示される
ci.__iter__() # iterをよんでみる→ 表示されるもの:イテレータが呼び出されたよ
ci.__next__() # nextをよんでみる→ 表示されるもの:nextが呼び出されたよ
print(ci.limit) #当然変わらず10が表示される
print(ci.counter) #カウントアップしたため、0が表示される
ci.__next__()
print(ci.counter) #カウントアップしたため、1が表示される

こんな所作をイメージしています。呼び出すたびに1が足されていくだけですね。実行結果も確認していきましょう。

自作イテレータ実行

さて、このイテレータがfor文で使えるかも実験です。さっきの続きから処理していきますので、表示されるのはカウントアップされていく数値+”nextが呼び出されたよ”が10まで続きます。

for i in ci:
    print(ci.counter)

実行結果です。ちゃんとイテレータとして機能していますね。

では、10(リミット数)までイテレータとして使い終わるとどうなるかという点も確認が必要ですね。では、for文の後にさらに1回nextを呼び出してみましょう

for i in ci:
    print(ci.counter)

ci.__next__()

stopIteration()関数が動作して、止まるようになっていますね。イテレータ関数は基本上限数(listiteratorの場合は、リストの要素数)まで利用すると再度初期化しない限り、そのオブジェクトでは動作が止まるように設計されています。

ここが、list等型自体をイテレータにせずに、iterable objectにする要因の一つですね。

(参考)コード一覧

class CountUpIterator:
    def __init__(self, limit=5):
        self.limit = limit
        self.counter = -1
    def __iter__(self):
        print('イテレータが呼び出されたよ')
        return self
    def __next__(self):
        print('nextが呼び出されたよ')
        self.counter += 1
        if self.counter > self.limit:
            raise StopIteration()
        return self.counter

ci = CountUpIterator(10) #limitを10にしてイテレータを初期化
print(ci.limit) #初期化した10が表示される
print(ci.counter) #初期値の-1が表示される
ci.__iter__() # iterをよんでみる→ 表示されるもの:イテレータが呼び出されたよ
ci.__next__() # nextをよんでみる→ 表示されるもの:nextが呼び出されたよ
print(ci.limit) #当然変わらず10が表示される
print(ci.counter) #カウントアップしたため、0が表示される
ci.__next__()
print(ci.counter) #カウントアップしたため、1が表示される
for i in ci:
    print(ci.counter)

ci.__next__()

まとめ

長々とiteratorについてみてきましたが、少しは理解の足しになったでしょうか。。

ちょっと書きながら回りくどいなぁと思う部分も多かったですが、一つ一つ理解していくことが、脱超初級の目標ですので、悪しからず。

次回は、generatorについて勉強していきたいと思います。generatorもiteratorの一つですので、あくまでも今回の延長線上と思って、まずはiterator/iterable object自体の理解をある程度形にしていきたいですね。

連載目次: python超初級脱出シリーズ

pythonの入門編を終えた後に、supper_beginnerクラスを脱出するためのシリーズです。unitテストやフレームワークの使い方に行く前に、少しpythonの知識を深めたい人用の記事です。

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

コメント

  1. […] […]