イテラブル、イテレータ、シーケンスは、Pythonの本やマニュアルなどに出てくる言葉ですが、特に初学者には意味の違いがあまりピンとこないと思います。これらの関係と違いを、例を多めに用いてじっくり説明してみようと思います。
この記事が、Pythonに関するテキストやドキュメントがよりよくわかるための参考になればうれしいです。
ひとことで言うと、それぞれは何なのか
どれもオブジェクトの性質を表す言葉です。大まかには以下のような性質です。
- イテラブル(iterable) 「何かを1つずつ取り出せる」という性質
- イテレータ(iterator) 「何かを1つずつ返す動作をする」という性質
- シーケンス(sequence) 「いくつかのものが一列に並んだデータである」という性質
このうち複数の性質を持ったオブジェクトもあります。例えばリストは、イテラブルであり、またシーケンスでもあります。それぞれの代表的なオブジェクト(型)は以下のようなものです。(ちょっと順序を入れ替えます)
-
シーケンス 文字列、リスト、タプル、
rangeオブジェクトなど -
イテレータ
zipオブジェクト、enumerateオブジェクトなど -
イテラブル シーケンスとイテレータ全部、それに加えて集合や辞書、
辞書.items()が返すオブジェクト(ビューオブジェクト)など
3つに共通なのは、どれも複数のデータを扱うためのオブジェクトだということです。(文字列なら複数の文字、リストやタプルなら複数の要素、など)
性質と特徴
イテラブル、イテレータ、シーケンスは以下の性質や特徴を持ちます。
イテラブル
イテラブルは、何かを1つずつ取り出せるという性質を持っていて、for文のinの後に指定できます。
for 変数 in イテラブル:
...
例えば集合(set)型のオブジェクトはイテラブルです。
greetings = {"こんにちは", "どうも", "やあ"} # 集合オブジェクトをディスプレイで作る
for greet in greetings:
print(greet)
実行結果
$ python3 iterable.py
やあ
こんにちは
どうも
$
集合の要素が1つずつ取り出されています。集合では要素間に順序がないので、取り出された順序は適当です。
イテレータ
イテレータは__next__()というメソッドを持っていて、それを呼び出すと何かを1つずつ返します。下の例では、まず組み込み関数reversed()を使って、リストの要素を逆順に返すイテレータを得て、そのイテレータに、要素を1つずつ返させています。
L = [2, 3, 5]
print(L)
iterator = reversed(L) # リストの要素を逆順に返すイテレータを得る
print(iterator.__next__())
print(iterator.__next__())
実行結果
__next__()を2回だけ呼び出しているので、逆順である5, 3, 2のうち最初の2つである5と3が返ってきます。
$ python3 reverse.py
[2, 3, 5]
5
3
$
組み込み関数next()にイテレータを渡しても同じことができます。
L = ["おはよう", "こんにちは", "こんばんは"]
print(L)
iterator = reversed(L)
print(next(iterator))
print(next(iterator))
実行結果
$ python3 reverse2.py
['おはよう', 'こんにちは', 'こんばんは']
こんばんは
こんにちは
$
メソッド__next__()は、アンダースコアが最初についていることからわかるように、内部的に使うもので、ただ要素を1つ得たい場合には普通は組み込み関数next()のほうを使いますが、オブジェクトが特定のメソッドを持っていることがわかりやすいように、以下では主に__next__()で説明します。
イテレータの重要な特徴は、状態を持ち、__next__()が呼ばれると状態が変わることです。そのため、__next__()を続けて呼び出すことで、先の例(reverse.py)のように要素を次々と取り出せます。
シーケンス
シーケンスは、イメージとしては、何かが一列に並んだデータで、その要素それぞれがインデックスを持っています。[ ]演算子を使うと、指定したインデックスの位置にある要素が得られます。
string = "こんにちはPython"
print(string[2])
print(string[6])
実行結果
$ python3 sequence.py
に
y
$
文字列が「こんにちはPython」なので、インデックス2の位置にある「に」と、インデックス6の位置にある「y」が表示されています。
それらの関係
3者の間には以下のような関係があります。
-
シーケンスはイテラブルである
シーケンスからは要素を1つずつ取り出せるので、すべてのシーケンスはイテラブルです(for文のinの後に指定できる)
例えば文字列はシーケンスなので、同時にイテラブルでもあり、for文のinの後に指定できます。
for character in "こんにちは、Python":
print(character)
実行結果
$ python3 sequence_is_iterable.py
こ
ん
に
ち
は
、
P
y
t
h
o
n
$
-
イテレータはイテラブルである
イテレータからも要素を1つずつ取り出せるので、すべてのイテレータはイテラブルです(for文のinの後に指定できる)
下の例は、要素を逆順に取り出すイテレータをfor文のinの後に指定しています。
iterator = reversed("こんにちは") # 要素を逆順に取り出すイテレータを得る
for character in iterator:
print(character)
実行結果
$ python3 iterator_is_iterable.py
は
ち
に
ん
こ
$
-
イテラブルからイテレータを作れる
イテラブルを組み込み関数iter()に渡すと、そのイテラブルの要素を1つずつ返すイテレータが得られます。
文字列やリストはイテラブルなので、以下のように関数iter()を使ってそれらからイテレータを得て、要素を1つずつ取り出せます。
string = "あいうえお"
print(string)
iterator = iter(string)
print(next(iterator))
print(next(iterator))
L = [1, 4, 9, 16]
print(L)
iterator2 = iter(L)
print(next(iterator2))
print(next(iterator2))
実行結果
$ python3 iter.py
あいうえお
あ
い
[1, 4, 9, 16]
1
4
$
イテレータもイテラブルなので、関数iter()に渡してイテレータを得ることができますが、その場合、関数iter()は、引数として渡したイテレータをそのまま返します。(つまり、iter()は何もしません)
reverse_iterator = reversed(["X", "Y", "Z"])
its_iterator = iter(reverse_iterator)
print(id(reverse_iterator), id(its_iterator))
for item in its_iterator:
print(item)
実行結果
$ python3 iterator_of_iterator.py
4307398560 4307398560
Z
Y
X
$
最初に、iter()に引数として渡したイテレータと、iter()から戻り値として返ってきたイテレータの、それぞれのオブジェクトIDを表示しています。IDが同じなので、渡したオブジェクトがそのまま返ってきたことがわかります。返ってきたイテレータからfor文で要素を取り出して出力すると、もちろんX, Y, Zが逆順に表示されます。
違い
イテラブルとシーケンスの違い
すべてのシーケンスはイテラブルです(集合の数式風に書くと、$\text{シーケンス}\subset\text{イテラブル}$)。文字列やリスト、タプル、rangeオブジェクトなどはシーケンスで、だからイテラブルでもあります。
イテラブルだけどシーケンスでないものは、組み込み型では集合や辞書などです。それらの要素には「何番目の要素である」という性質がないため、インデックスを使って取り出せません。
また、(i*i for i in range(5)) のようなジェネレータ式で生成されるジェネレータオブジェクトは、イテレータであり、つまりイテラブルでもありますが、シーケンスではありません。
generator = (i*i for i in range(5))
for item in generator:
print(item)
generator = (i*i for i in range(5))
print(generator[2])
実行結果
$ python3 generator.py
0
1
4
9
16
Traceback (most recent call last):
File "/Users/johndoe/qiita/generator.py", line 6, in <module>
print(generator[2])
TypeError: 'generator' object is not subscriptable
$
このように、イテラブルなのでforで要素を1つずつ取り出せますが、インデックスで要素にアクセスできません。
イテラブルとイテレータの違い
すべてのイテレータはイテラブルです($\text{イテレータ}\subset\text{イテラブル}$)。イテラブルだけどイテレータでないのは、文字列やリスト、タプル、rangeオブジェクト、集合、辞書など、よく使う、複数のオブジェクトへの参照を持つ型(コンテナ型といいます)などです。
iterator = iter("こんにちは")
print(iterator.__next__())
sequence = "こんにちは"
print(sequence.__next__())
実行結果
$ python3 non_iterator.py
こ
Traceback (most recent call last):
File "/Users/johndoe/qiita/non_iterator.py", line 5, in <module>
print(sequence.__next__())
AttributeError: 'str' object has no attribute '__next__'
$
イテレータからは「こ」が取り出せていますが、シーケンスである文字列は__next__()メソッドを持っていないため、エラーになりました。
見分け方
あるオブジェクトがイテラブルか、イテレータか、シーケンスか、どれなんだろう?と思ったときには、以下のようにするとだいたい見分けられます。(正確に、ではないです。型の定義次第なので)
イテラブルかどうか
組み込み関数iter()に引数として与えて、何かが返ってくればイテラブルです。(返ってくるのはイテレータです)
iterable = dict(x=2, y=3) # イテラブルの例
iterator = reversed([1, 10, 100]) # イテレータの例
sequence = "こんにちは" # シーケンスの例
print(iter(iterable))
print(iter(iterator))
print(iter(sequence))
実行結果
$ python3 is_iterable.py
<dict_keyiterator object at 0x10583fe50>
<list_reverseiterator object at 0x1010b6fa0>
<str_iterator object at 0x1010b6cd0>
$
どれについても、何かオブジェクトが返ってきているので、いずれもイテラブルです。
イテレータかどうか
そのオブジェクトのメソッド__next__()を呼び出すか、組み込み関数next()に引数として与えたときに、何かオブジェクトが返ってくるか、例外StopIterationが発生すれば、イテレータです。StopIterationは、要素が残っていない状態で次の要素を取り出そうとした場合にイテレータが送出する例外です。
イテレータではないシーケンスの場合
まずは関数next()を使ってみます。
sequence = "abc"
print(next(sequence))
実行結果
$ python3 is_iterator1.py
Traceback (most recent call last):
File "/Users/johndoe/qiita/is_iterator1.py", line 2, in <module>
print(next(sequence))
TypeError: 'str' object is not an iterator
$
文字列はイテレータではないので、next()に渡して呼び出したときにTypeErrorというエラーが発生し、エラーメッセージが「strオブジェクトはイテレータではない」となっています。
イテレータではないイテラブルの場合
__next__()メソッドを使ってもチェックできます。
iterable = dict() # 空の辞書
print(iterable.__next__())
実行結果
$ python3 is_iterator2.py
Traceback (most recent call last):
File "/Users/johndoe/qiita/is_iterator2.py", line 2, in <module>
print(iterable.__next__())
AttributeError: 'dict' object has no attribute '__next__'
$
辞書オブジェクトはイテラブルですがイテレータではなく、__next__()メソッドを持っていません。
イテレータの場合
zip_object = zip(["赤", "青", "緑"], ["red", "blue", "green"])
print(next(zip_object))
empty_iterator = reversed([])
print(next(empty_iterator))
実行結果
$ python3 is_iterator3.py
('赤', 'red')
Traceback (most recent call last):
File "/Users/johndoe/qiita/is_iterator3.py", line 5, in <module>
print(next(empty_iterator))
StopIteration
$
zip()が返すオブジェクトも、reversed()が返すオブジェクトも、イテレータです。
next()の呼び出しで、zipオブジェクトからは('赤', 'red')が返ってきました。reversedオブジェクトは、この例では要素を持っていないので(空リスト[]を引数として与えたため)例外StopIterationが発生しましたが、これもイテレータの動作です。
別のやり方としては、イテレータを組み込み関数iter()に渡すと自分自身が返るので、戻り値が引数と同一のオブジェクトかどうか(オブジェクトIDを比較したり、is演算子で確認する)でも判定できます。
シーケンスかどうか
シーケンスオブジェクトはたいていindex()メソッドを持っています。それがあればシーケンスと思ってよいでしょう。組み込み関数hasattr()でindexという名前の属性があるかチェックしてから、その属性を組み込み関数getattr()で得て、それがメソッドか(呼び出せるか)を組み込み関数callable()で確認するとわかります。
sequence = "こんにちは" # これはシーケンス
print(hasattr(sequence, "index") and callable(getattr(sequence, "index")))
non_sequence = {0: "zero", 1: "one"} # シーケンスでない
print(hasattr(non_sequence, "index") and callable(getattr(non_sequence, "index")))
実行結果
$ python3 is_sequence1.py
True
False
$
もっと雑に、引数なしでindex()メソッドを呼んでみて、「引数が足りない」というエラーが出たらindex()メソッドがあるのでシーケンス、「そんな属性はない」というエラーになったらシーケンスでない、と判断することもできます。
"こんにちは".index()
実行結果
$ python3 is_sequence2.py
Traceback (most recent call last):
File "/Users/johndoe/qiita/is_sequence2.py", line 1, in <module>
"こんにちは".index()
TypeError: index() takes at least 1 argument (0 given)
$
最後の行にあるエラーメッセージが「型エラー:index()は少なくとも引数を1つ取ります(0個が与えられました)」となっていて、index()メソッドがあるとわかります。ここから文字列"こんにちは"はシーケンスだと判断できます。
{0: "zero", 1: "one"}.index()
実行結果
$ python3 is_sequence3.py
Traceback (most recent call last):
File "/Users/johndoe/qiita/is_sequence3.py", line 1, in <module>
{0: "zero", 1: "one"}.index()
AttributeError: 'dict' object has no attribute 'index'
$
こちらは「属性エラー:dictオブジェクトはindexという属性を持っていません」となりました。
まとめと、気をつけるべきこと
イテラブル、イテレータ、シーケンスは、オブジェクトが持っている性質で、おおまかには以下のようなものです(最初に書いたものの再掲です)。
- イテラブル 「何かを1つずつ取り出せる」という性質
- イテレータ 「何かを1つずつ返す動作をする」という性質
- シーケンス 「いくつかのものが一列に並んだデータである」という性質
オブジェクトは、このうち複数の性質を持っている場合があります。特に、
- イテレータは必ずイテラブルである
- シーケンスは必ずイテラブルである
という関係があります。イテレータとシーケンスの関係が気になるでしょうが、それらは違うものと思っておいてよいでしょう。(これも型の定義次第です)
これらはすべて、複数のオブジェクトを扱うために使われます。使い方の違いはおよそ以下のような感じです。
-
イテラブル 要素を1つずつ扱う(
for文などで) - イテレータ 要素を1つずつ得て、そのたびに状態が変わる
- シーケンス 要素をインデックスで指定してアクセスするとか、その並びの順で扱う
この3つの違いとして気をつけるべきなのは以下の点です。
- インデックスで要素を指定できるのはシーケンスだけである
- イテレータは、要素を取り出すたびに状態が変わるので、状態を意識する必要がある
特にこの2つめのイテレータの性質は、知っていないと意外な挙動にびっくりすることがあります。リストを反転する(逆順にする)例でこれを示します。
リストを反転するには、リストのメソッドreverse()や、組み込み関数reversed()が使えます。前者はそのリストをインプレースで(つまりリスト自身を)変更し、後者は要素を逆順に取り出せるイテレータを戻り値として返します。それぞれを普通にfor文で使うと以下のようになります。
L1 = ["reverseは", "リストを反転する", "メソッドです"]
L1.reverse()
print(L1)
for item in L1:
print(item)
L2 = ["reversedは", "イテレータを返す", "組み込み関数です"]
iterator = reversed(L2)
print(iterator)
for item in iterator:
print(item)
実行結果
$ python3 reverse_reversed1.py
['メソッドです', 'リストを反転する', 'reverseは']
メソッドです
リストを反転する
reverseは
<list_reverseiterator object at 0x1028f3fd0>
組み込み関数です
イテレータを返す
reversedは
$
reverse()メソッドを使ったほうでは、そのメソッドを実行すると反転したリストが得られるため、['メソッドです', 'リストを反転する', 'reverseは']というリストが出力されています。reversed()関数を使ったほうでは、その戻り値はイテレータなので、リストではなく<list_reverseiterator object at 0x1028f3fd0>という出力になっています。が、いずれの場合も、その後に要素が逆順に出力されていて、同じような動作をしています。
ここで、イテレータをそのままprintすると、上のような<list_reverseiterator object ...>などというよくわからない出力になるので、わかりやすいように、イテレータが持っているデータをリストにして出力することにします。list()コンストラクタの引数としてイテレータを与えるとそれができます。
L1 = ["reverseは", "リストを反転する", "メソッドです"]
L1.reverse()
print(L1)
for item in L1:
print(item)
L2 = ["reversedは", "イテレータを返す", "組み込み関数です"]
iterator = reversed(L2)
print(list(iterator)) # ここで反転結果をリストとして出力する
for item in iterator:
print(item)
変えたのは、コメントを付けたprint(list(iterator))の行だけです。
実行結果
$ python3 reverse_reversed2.py
['メソッドです', 'リストを反転する', 'reverseは']
メソッドです
リストを反転する
reverseは
['組み込み関数です', 'イテレータを返す', 'reversedは']
$
reverse()メソッドを使ったほうでは、意図した通り、順序が反転したリスト['メソッドです', 'リストを反転する', 'reverseは']が表示された後に、各要素が逆順に出力されています。しかしreversed()関数を使ったほうでは、順序が反転されたリスト['組み込み関数です', 'イテレータを返す', 'reversedは']は表示されていますが、その後にfor文があるのに、要素がまったく出力されていません。
この理由は、list(iterator)の呼び出しで、list()コンストラクタがiteratorの中身をすべて取り出してリストを作ったため、iteratorが「もう要素がない」状態になったためです。
このように、要素がどこまで処理されたかという状態を持たないシーケンス(ここではリスト)と、どこまで要素が取り出されたかという状態を保持しているイテレータは、イメージとしてはどちらも要素が並んだものですが、振る舞いが違うので、注意が必要です。
(おまけ)for文が便利すぎる
イテラブル、イテレータ、シーケンスの違いがわかりにくい理由の1つは、for文がそれらを同様に扱うためだと思います。for文のinの後には「データがひとつずつ取り出せるもの」(=イテラブル)なら何でも指定できて、そのデータを順に処理してくれるからです。
これが実現できている仕組みのポイントは、上のほうで書いたように、イテレータをiter()関数に与えると、そのイテレータ自身が返るところにあります。
for文の動作では、まずinの後にある式(イテラブルが得られるもの)を評価してオブジェクト(イテラブル)を得てから、そのイテラブルのイテレータを作って(iter()の引数に与えて呼び出して戻り値を得るイメージ)、そのイテレータから要素を1つずつ取り出してループの本体を実行します。
for文のこの性質によって、inの後にシーケンスを与えれば、そのシーケンスのためのイテレータが作られて使われ、inの後にイテレータを与えれば、そのイテレータ自身が使われます。
そのため、Pythonを勉強し始めたくらいの段階では、イテレータとシーケンスを区別する必要がほとんどありません。より一般に、Pythonには「イテラブルなら扱う」という機能が多く、そういう機能を使うときにはこの2者の区別を意識しなくていいのです。以下のような、代入によるアンパックもそうです。
# シーケンス
x, y, *z = [1, 2, 3, 4, 5]
print("x ==", x)
print("y ==", y)
print("z ==", z)
# イテレータ(zipオブジェクト)
a, b, *c = zip(["赤", "橙", "黄", "緑", "青"], ["red", "orange", "yellow", "green", "blue"])
print("a ==", a)
print("b ==", b)
print("c ==", c)
実行結果
$ python3 unpack.py
x == 1
y == 2
z == [3, 4, 5]
a == ('赤', 'red')
b == ('橙', 'orange')
c == [('黄', 'yellow'), ('緑', 'green'), ('青', 'blue')]
$
しかし、先ほどの例のように、イテレータの状態が実行にかかわるような場合には、「データが並んだもの」と漠然と理解しているだけでは不十分なことがあるので、より進んだ・間違いのないプログラミングをするためには、これらの違いを知っておいたほうがよいでしょう。
参考文献……でもないけれど
筆者は以下のPythonの入門書を出版しています。
冨永和人『基礎力がつくPythonプログラミング入門』(コロナ社)
本としてコンパクトにまとめるために、泣く泣く説明を省いたり圧縮したりした箇所が多々あります。この記事の内容は、本の尺に収まらずにカット(圧縮)した説明のひとつで、初学者の方や、Pythonのプログラムはだいぶ書けるけどこういった用語や概念がまだいまいちピンとこないと感じる技術者の皆さんに役立てて頂ければと思い、ここに転生させます。
もしPythonの他の基礎的な部分に不安があるようであれば、拙著もご参考になるかと思います。初心者にもわかりやすい説明を心掛けて書きました。