Pythonのイテレータの使い方【わかりやすく解説】

Pythonのイテレータの使い方についてわかりやすく解説します。

Pythonのイテレータの使い方【わかりやすく解説】

イテレータとは?

イテレータは、プログラミングにおいて、リスト(配列)などの連続したデータ構造を一つずつ順番にアクセスするための仕組みです。

一般的にイテレータは、そのデータ構造を順番に反復処理するために使用され、誤解を恐れずいうのであれば、リストなどのデータ構造をコピーして、現在どの位置までアクセスしたかの状態も保持するオブジェクトと言えます。

この概念を理解するために、まずイテレータと反復可能オブジェクトについて理解しましょう。

イテレータと反復可能オブジェクト(Iterable)

Pythonにおいて、リストや文字列、タプルなどの連続したデータを保つオブジェクトを反復可能オブジェクト(Iterable)と呼びます。要素の順序が決まっていない、辞書型やファイルオブジェクトも反復可能オブジェクトです。

反復可能オブジェクト(Iterable)は、反復処理で呼び出されたときに、そのオブジェクトが持つ要素を一つずつ返すことができます。例えば、forループでリストの要素を一つずつ取り出して処理ができるのは、Pythonのリストが反復可能オブジェクト(Iterable)だからです。

一方で、イテレータとは、データの流れを表現するオブジェクトです。イテレータは、上記のような反復可能オブジェクトからデータをコピーし、次に取り出す要素は何か?どこまで要素を取り出したか?という状態を保持してくれます

イテレータがあることで、for文などの繰り返し処理で、各要素に1度ずつアクセスすることが容易になります。繰り返し処理をする場合に、次にアクセスする要素がわからないと大変なことになりますよね。

少し分かりづらいので、具体的な例を用いて解説します。

iter関数でイテレータを取得する

詳細は後ほど解説しますが、反復可能オブジェクトは__iter__()メソッドを持っています。組み込み関数のiter()に反復可能オブジェクトを渡すと、そのオブジェクトの__iter__()メソッドが、そのオブジェクトのイテレータを返してくれます。

以下の例では、数字の1〜3までを保持したリストを用いて、反復可能オブジェクト(Iterable)とイテレータがどういうものかを説明します。

# リストを定義
nums = [1, 2, 3]
print(type(nums))
# <class 'list'>


# 反復可能オブジェクトであれば、__iter__()メソッドを持っている
nums.__iter__()
# <list_iterator object at 0x7fad89ebc3a0>


# iter()関数でイテレータを取得
it = iter(nums)
print(it)
# <list_iterator object at 0x7fad89d68b20>

next関数でイテレータから要素を取得する

イテレータは、内部的には__iter__()__next__()というメソッドを実装しています。__iter__()はイテレータ自身を戻り値とするメソッドで、__next__()は自身が保有する要素を一つだけ返すメソッドです。__next__()で順番にイテレータから要素を取り出し、要素が尽きたらStopIteration例外となります。

Pythonの組み込み関数のnext()を利用することで、この__next__()メソッドから返される要素を取得することができます。

# next()関数でイテレータから要素を一つずつ取り出す
print(next(it))
# 1
print(next(it))
# 2
print(next(it))
# 3

# イテレータの要素をすべて取り出した後に__next__()を呼び出すと
# StopIterationエラーになる
print(next(it))
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# StopIteration

Pythonのイテレータの使いどころ

イテレータを使うことでどのようなメリットがあるのでしょうか?Pythonのforループも内部的にはイテレータを使っているのですが、仮に、リストなどのデータ構造を直接反復処理する場合には、以下のような問題点があります。

  • メモリ効率が悪い

リストや辞書などのデータ構造を反復処理するには、データ構造全体を一度にメモリに読み込む必要があります。これにより、データセットが大きい場合には、メモリ使用量が大きくなることがあります。

  • データアクセスが遅い

リストを用いた反復処理をするためには、データ構造の最初から最後までを一度にスキャンする必要があります。これにより、データアクセスが遅くなることがあります。

これらの問題を解決するために、Pythonではイテレータを使用することができます。イテレータは、データ構造を一つずつ順番にアクセスすることで、効率的なデータアクセスと処理速度の向上を実現します。また、データ構造全体を一度にメモリに読み込む必要がないため、メモリ効率が良いです

そのため、大きなデータを扱う場合などには、イテレータを使用することが多いです。

また、イテレータを使用することで、コードがスマートになり、データ構造を隠蔽することができるため、使い勝手も良くなります。

例えば、以下のように、大きなファイルを一行ずつ読み込んで処理するときに、for ループを使用したイテレータを使用することができます。

with open("big_file.txt") as f:
    for line in f:
        # lineには、1行ずつ読み込まれた文字列が格納されている
        process_line(line)

Pythonのイテレータで使用できるデータ型

Pythonでは、次のようなデータ構造を使用してイテレータを作成することができます。

  • リスト(list)
  • 辞書(dict)
  • タプル(tuple)
  • 文字列(str)
  • 集合(set)
  • ファイル

それぞれのデータ型に対して、iter()関数を使うことによってイテレータに変換することができます。イテレータに対しては、next()関数を使うことでその要素を一つずつ取り出すことができます。イテレータの全ての要素を取り出すとStopIterationエラーになります。

以下、それぞれのデータ型でイテレータを使う方法を列挙します。

# リストからイテレータを作成する
my_list = [1, 2, 3]
it = iter(my_list)
print(it)
# <list_iterator object at 0x7fb6744cd2e0>
print(next(it))
# 1
print(next(it))
# 2
print(next(it))
# 3
print(next(it))
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration


# 辞書からイテレータを作成する
my_dict = {'a': 1, 'b': 2, 'c': 3}

# 辞書から、キーを要素とするイテレータを作成
it = iter(my_dict)
print(it)
# <dict_keyiterator object at 0x7fb674522e50>

# 辞書から、値を要素とするイテレータを作成
it = iter(my_dict.values())
print(it)
# <dict_valueiterator object at 0x7fb674522e00>


# タプルからイテレータを作成する
my_tuple = (1, 2, 3, 4, 5)
it = iter(my_tuple)
print(it)
# <tuple_iterator object at 0x7fb674521190>


# 文字列からイテレータを作成する
string = "abc"
it = iter(string)
print(it)
# <str_iterator object at 0x7fad89d68b20>
print(next(it))
# a
print(next(it))
# b


# 集合(Set)からイテレータを作成する
s = {1, 2, 3}
it = iter(s)
print(it)
# <set_iterator object at 0x7fad89f21040>
print(next(it))
# 1
print(next(it))
# 2


# ファイルからイテレータを作成する(50音が1行ずつ並ぶファイルの場合)
with open("file.txt") as f:
    it = iter(f)
    print(next(it))
    print(next(it))
# あいうえお
# かきくけこ

また、 iter() 関数を使用することで、自分でイテレータを定義することもできます。自分でイテレータを定義する場合は、イテレータを定義するクラスに、 __iter__() メソッドと __next__() メソッドを定義する必要があります。

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        result = self.data[self.index]
        self.index += 1
        return result

it = MyIterator([1, 2, 3])
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3

Pythonにおけるイテレータとリストの違い

よくある疑問点として、イテレータとリストの違いがよくわからないという点があります。主な違いのポイントをまとめると、以下のようになります。

  • イテレータは、データ構造を一つずつ順番にアクセスすることで、効率的なデータアクセスを実現するオブジェクトです。一方、リストは、データを一括で格納するデータ構造のことです。
  • イテレータは、データ構造を反復処理するときに使用されます。一方、リストは、データを格納するだけでなく、データを加工したり、検索したりすることもできます。
  • イテレータは、データ構造を隠蔽することができます。一方、リストは、データ構造を直接参照することができます。
  • イテレータは、データ構造を一つずつ順番にアクセスするため、データを追加したり、削除したりすることができません。一方、リストは、データを追加したり、削除したりすることができます。

要するに、Pythonにおけるイテレータとリストは、似ているようで全然別の概念です。イテレータは、効率的なデータアクセスを実現するために使用され、リストは、データを格納したり、加工したりするために使用されます。

Pythonでイテレータをリストに変換する方法

Pythonで、イテレータをリストに変換するには、list関数を使用することができます。

例えば、以下のように、イテレータを作成し、そのイテレータをlist関数に渡すことで、リストに変換することができます。

# イテレータを作成
it = iter([1, 2, 3, 4, 5])

# イテレータをリストに変換
my_list = list(it)

print(it)
# <list_iterator object at 0x7fb6744cd3d0>

print(my_list)
# [1, 2, 3, 4, 5]