【Python】イテラブルオブジェクトとイテレータとは?

Python

この記事では、Pythonのイテラブルオブジェクトイテレータについて解説します。よくリストなどやfor文あたりで見かけますが、一体どのようなものなのでしょうか?

それでは、一緒に確認していきましょう!

イテラブルオブジェクトとは?

イテラブルオブジェクトとは、簡単に言うと「反復可能なオブジェクト」のことを言います。

以下のような型があります。

  • シーケンス型(リスト、タプル、range)
  • マッピング型(辞書)
  • テキストシーケンス型(文字列)
  • バイナリシーケンス型(bytes、bytearray、memoryview)

繰り返し処理の仕組み

例としてfor文が繰り返し処理する流れを見てみましょう!

  1. for文が指定されたイテラブルオブジェクトに対して__iter__メソッドを呼ぶ
  2. __iter__メソッドは__next__メソッドが定義されているオブジェクトを返す
  3. 返されたオブジェクトに対して__next__メソッドを繰り返し呼び出す
  4. __next__メソッドがStopIteration例外を送出するとループが終了する

このように繰り返し処理をしています。
つまり、イテラブルオブジェクトとは__iter__メソッドが定義されているオブジェクトのことです。

イテレータオブジェクトを生成

__iter__メソッドを呼び出すとイテレータオブジェクトが返される。

l = [1, 2, 3]

# iter関数は__iter__メソッド呼び出す
print(iter(l))

# 直接呼び出す
print(l.__iter__())

実行結果

<list_iterator object at 0x10d8402d0>
<list_iterator object at 0x10d84d3d0>

for文で繰り返し処理する際、イテラブルオブジェクトに対して__iter__メソッドが呼び出されています。ということは、イテラブルオブジェクトはイテレータオブジェクトを生成して繰り返し処理をしていることがわかります。

イテレータとは?

イテレータとは、イテレータオブジェクトという型の1つで「__next__メソッドによって要素を取り出せるオブジェクト」です。__iter__メソッドで返した__next__メソッドが定義してあるオブジェクトを元に、イテレータオブジェクトが生成されます。

イテレータオブジェクトを使ってみる

試しにイテレータオブジェクトから要素を取得してみましょう。

for文で使ってみる

リストと同じように処理されました。

l = [1, 2, 3]

iterator = iter(l)

for i in iterator:
    print(i)

実行結果

1
2
3

次の要素

next関数を使って次の要素を取得できます。
次の要素がない場合は、StopIteration例外が送出されます。

l = [1, 2, 3]

iterator = iter(l)

print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

実行結果

1
2
3
Traceback (most recent call last):
    print(next(iterator))
StopIteration

イテレータプロトコル

__iter__メソッドと__next__メソッドを合わせてイテレータプロトコルと呼ばれます。
イテレータプロトコルを実装することで、ユーザー定義クラスにイテラブルオブジェクトとしての振る舞いを追加できます。

__iter__メソッド

__next__メソッドが定義されているオブジェクトを返す。

def __iter__(self):
    return __next__メソッドが定義されているオブジェクト

自身に__next__メソッドが定義されているならselfを返すことができる。

def __iter__(self):
    return self

__next__メソッド

呼び出されるたびに要素を返します。
返す要素がなければStopIteration例外を送出します。

def __next__(self):
    # 要素を返す
    # 返す要素がなければStopIteration例外を送出

イテレータプロトコルを実装してみる

以下のコードは、コンストラクタで受け取った不特定多数の要素をfor文でループ処理できるようにしました。

class MyIterator:

    def __init__(self, *args):
        self.__args = args
        self.__index = 0

    # __next__メソッドを定義しているオブジェクトを返す
    def __iter__(self):
        print('__iter__')
        return self

    # 呼び出されるたびに新しい要素を返す
    # 最後にStopIterationを返す
    def __next__(self):
        print('__next__')
        if self.__index == len(self.__args):
            print('StopIteration')
            self.__index = 0
            raise StopIteration()
        arg = self.__args[self.__index]
        self.__index += 1
        return arg


myiter = MyIterator(1, 2, 3)

for arg in myiter:
    print(arg)

実行結果

__iter__
__next__
1
__next__
2
__next__
3
__next__
StopIteration

簡単にメソッドの内容を見てみましょう!

__iter__メソッド

__iter__メソッドではselfを返しました。

def __iter__(self): 
    print('__iter__') 
    return self

__next__メソッド

呼び出されるたびに__index変数に1を足して、毎回新たな要素を返します。__index変数と__args変数の長さが同じになったら返す要素がないのでStopIteration例外を送出します。

def __next__(self):
    print('__next__')
    if self.__index == len(self.__args):
        print('StopIteration')
        self.__index = 0
        raise StopIteration()
    arg = self.__args[self.__index]
    self.__index += 1
    return arg

イテレータプロトコルをバラバラに実装する

先ほどのMyIterationクラスをバラバラにして実装してみます。

# __iter__メソッドを定義したクラス
class MyIter:

    def __init__(self, *args):
        self.__args = args

    def __iter__(self):
        print('__iter__')
        # MyNextを呼び出す
        return MyNext(*self.__args)


# __next__メソッドを定義したクラス
class MyNext:

    def __init__(self, *args):
        self.__args = args
        self.__index = 0
    
    def __next__(self):
        print('__next__')
        if self.__index == len(self.__args):
            print('StopIteration')
            self.__index = 0
            raise StopIteration()
        arg = self.__args[self.__index]
        self.__index += 1
        return arg

myiter = MyIter(1, 2, 3)

for arg in myiter:
    print(arg)

実行結果

__iter__
__next__
1
__next__
2
__next__
3
__next__
StopIteration

他のクラスでも同じ__next__メソッドを使いたい場合は、そのためのクラスを作成すると同じような実装を何度もしなくて済みます。また、複雑になりすぎる場合も、外部に実装することでクラスをすっきりさせることができます。

iter関数を使う

iter関数は、オブジェクトに定義されている__iter__メソッドを呼び出します。

使い方

引数に指定するオブジェクトは、__iter__メソッドを定義しているオブジェクトでなければなりません。

iter(オブジェクト)

サンプル

以下のコードでは、iter関数を使って可変長引数(タプル)の__iter__メソッドを呼び出して、自らの__iter__メソッドで返しています。

class MyIter:
    def __init__(self, *args):
        self.__args = args

    def __iter__(self):
        return iter(self.__args)


myiter = MyIter(1, 2, 3)
for arg in myiter:
    print(arg)

実行結果

1
2
3

これを少し応用することで、以下のように、インスタンス変数をまとめてfor文で取得できるようなクラスを作成できます。

class Person:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

def __iter__(self):
    # インスタンス変数をタプルにまとめて、イテレータオブジェクトとして返す
    return iter((self.name, self.age, self.gender))


p = Person('田中太郎', '20', '男')

for arg in p:
    print(arg)

実行結果

田中太郎
20
男

まとめ

この記事では、Pythonのイテラブルオブジェクトとイテレータについて解説しました。

今回のおさらい

  • イテラブルオブジェクトとは「反復可能なオブジェクト」である。
  • 以下の2つのメソッドを定義することでイテレータを実装できます。
  • __next__メソッドが定義されているオブジェクトを返す__iter__メソッドの定義
  • 要素にアクセスし、最後にStopIteration例外を送出する__next__メソッドの定義

これらのことを覚えておけば、イテレータを実装することはとても容易です。

それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ

タイトルとURLをコピーしました