4-2. イテラブルとイテレータ

イテラブルとイテレータについて説明します。

参考

for文による繰り返しとファイルオブジェクト

ファイルオブジェクトは、for文の in の後に指定することができます。 このとき、ファイルから各行の文字列(改行文字を含む)を順次取り出します。

[1]:
lines = []
with open('sample.txt', 'r') as f:
    for line in f:
        lines.append(line)
lines
[1]:
['Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n',
 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n',
 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n']

for文による行の取り出しは、readline メソッドと同様に、ファイルオブジェクトを消費します。

ファイルオブジェクト f をfor文で処理をすると、f が終端に到達するまで繰り返されます。 したがって、再度 f をfor文に与えても何も実行されません。

[2]:
with open('sample.txt', 'r') as f:
    print('---- 最初 ----')
    for line in f:
        print(line)
    print('---- もう一度 ----')
    for line in f:
        print(line)
---- 最初 ----
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

---- もう一度 ----

ファイルをfor文によって二度読みたい場合は、 もう一度ファイルをオープンして、ファイルのオブジェクトを新たに生成してください。

練習

文字列 name をファイル名とするファイルの最後の行を文字列として返す関数 last_line(name) を、 ファイルオブジェクトに対するfor文を用いて定義してください。

[3]:
def last_line(name):
    ...

定義ができたら、次のセルを実行して、エラーがでないことを確認してください。

[4]:
assert last_line('sample.txt') == 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/tmp/ipykernel_5377/557157612.py in <module>
----> 1 assert last_line('sample.txt') == 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'

AssertionError:

for文とイテラブルとイテレータ

さて、3-2でも示したように、for文は、ファイルオブジェクトを含む様々なオブジェクトに対して繰り返す処理を記述できます (1-3で説明があったように、Pythonにおける値はオブジェクトと総称されます。)

for文によって繰り返すことができるオブジェクトのことを総称して、イテラブル (iterable) と呼びます。

for文が、様々なデータ型をイテラブルとして統一的に扱えるのは、繰り返して取り出す操作を表現するイテレータ (iterator) を経由するからです。

iterateは繰り返すという意味なので、itaratorは繰り返すもの(反復子とも呼ばれる)、iterableは繰り返すことができるもの、という意味です。

iter

組み込み関数 iter は、イテラブルからイテレータを作ります。たとえば、

[5]:
it = iter([0,1,2])

この it は、0 1 2 を順に取り出すイテレータです。

このとき、iter に渡されるオブジェクトの種類に応じて、適切なイテレータが構成されます。 この仕組みによって、様々なデータ型を統一的に扱えるわけです。

next

さて、iter が返したイテレータは、どのように使うのでしょうか。

組み込み関数 next は、イテレータに対して繰り返しを1回分先に進める操作を与えます。具体例を見ましょう。

[6]:
it = iter([0,1,2])
next(it)
[6]:
0
[7]:
next(it)
[7]:
1
[8]:
next(it)
[8]:
2

next(it) を呼び出す毎に、it から順に要素が取り出されています。

与えられたイテレータが繰り返しの終端に到達していたときには、StopIteration という例外(処理を緊急停止させる割り込み)が発生します。

[9]:
next(it)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_5377/600241529.py in <module>
----> 1 next(it)

StopIteration:

for文の仕組み

for文は、実は iter next StopIteration を使ったwhile文の形で表現できます。具体的には、

[10]:
for x in [0,1,2]:
    print(x)
0
1
2

このfor文は、次のwhile文と等価です。

[11]:
try:
    it = iter([0,1,2])
    while True:
        x = next(it)
        print(x)
except StopIteration:
    pass
0
1
2

このtry-except文は、発生した例外を捉えて、適切に処理するための構文です。 ここでは、発生した StopIteration を捉えて、何もせずに(pass)次に処理を進めるという意味になります。 結果として、コードセルの実行が成功裡に完了しています。 (例外とtry-except文の詳細は、公式チュートリアルを参照してください。)

イテレータは特殊なイテラブル

イテレータ自身も実はイテラブルです。したがって、イテレータをfor文で繰り返すことができます。

[12]:
it = iter([0,1,2])
for x in it:
    print(x)
0
1
2

しかし、単なるイテラブルではありません。1つのfor文で使い切りのイテラブルです。

[13]:
for x in it:
    print('これは実行されない')

これは、イテレータ it に対して iter を適用した時に、it 自体が返されるという仕組みによって実現されています。

[14]:
it = iter([0,1,2])
it is iter(it)
[14]:
True

この is は、2-2に説明がありますが、その両辺が同一オブジェクトかどうかを調べる演算子です。

最初のfor文の繰り返しによって終端に到達したイテレータ it は、その後の next(it) において StopIteration を発生させ続けます。 したがって、後続するfor文では、繰り返し処理に入ることなく即座に終了するわけです。 上に示したwhile文の形で考えると、わかりやすいでしょう。

練習

リストをもらって、そのイテレータを作り、最初の要素だけ取り出した後、 そのイテレータを返す関数 but_first(ls) を定義してください。

[15]:
def but_first(ls):
    ...

定義ができたら、次のセルを実行して、エラーがでないことを確認してください。

[16]:
it = but_first([0,2,4,6,8])
assert type(it) == type(iter([])) # type(it) では it は消費されない
assert list(it) == [2,4,6,8]
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/tmp/ipykernel_5377/1825202606.py in <module>
      1 it = but_first([0,2,4,6,8])
----> 2 assert type(it) == type(iter([])) # type(it) では it は消費されない
      3 assert list(it) == [2,4,6,8]

AssertionError:

ファイルオブジェクトはイテレータ

ファイルオブジェクトは readline メソッドによって、一行ずつ消費しながら、行の文字列を取り出せます。 そして、for文で末尾まで繰り返すと、for文で繰り返すことはもうできません。

ここまで読んだ方は既に気付いているでしょうが、ファイルオブジェクトはイテレータです。

[17]:
f = open('sample.txt', 'r')
[18]:
next(f)
[18]:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n'
[19]:
next(f)
[19]:
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n'
[20]:
next(f)
[20]:
'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'
[21]:
next(f)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_5377/4256387510.py in <module>
----> 1 next(f)

StopIteration:

ファイルオブジェクト f に対する f.readline()next(f) の振舞いは、f が終端に到達していないときは同一です。 f が終端にあるときは、f.readline()'' を返しますが、next(f)StopIteration を発生させます。

[22]:
f.close()

イテラブルはイテレータではない

イテラブルは一般に、イテレータではありません。 具体的には、リスト・タプル・文字列・辞書はイテラブルですが、イテレータではありません。 3-2で紹介した range 関数の返す range オブジェクトも、イテラブルですがイテレータではありません。

したがって、next が適用可能ではなく、iter の適用によって毎回別のイテレータが返されます。 つまり、これまで見てきたように、複数のfor文で何度も繰り返す処理が実行できます。

[23]:
xs = [1,2,3]
for x in xs:
    print(x)
for x in xs:
    print(x)
1
2
3
1
2
3
[24]:
r = range(3)
for x in r:
    print(x)
for x in r:
    print(x)
0
1
2
0
1
2

イテレータを返す enumerate

3-2で紹介した組み込み関数の enumerate は、イテレータを返します。

[25]:
it = enumerate([10,20,30])
[26]:
next(it)
[26]:
(0, 10)
[27]:
for x in it:
    print(x)
(1, 20)
(2, 30)
[28]:
for i, c in enumerate('ACDB'):
    print(i, '番目の文字 =', c)
0 番目の文字 = A
1 番目の文字 = C
2 番目の文字 = D
3 番目の文字 = B

一方、enumerate はイテラブルを引数として受け取ります。上の例で、リストも文字列もイテラブルです。

イテレータもイテラブルなので、enumerate の引数になり得ます。 したがって、ファイルオブジェクトも、次のように enumerate に与えることができます。

[29]:
with open('sample.txt', 'r') as f:
    for i, s in enumerate(f):
        print(i, '行目:')
        print(s)
0 行目:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

1 行目:
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

2 行目:
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

変数 i0 から順に増えていきます。変数 s には各行の文字列が代入されます。 i0 から始まりますが、各行の行番号と考えられます。

▲イテラブルとイテレータの定義

イテラブルとイテレータの形式的な定義をまとめます。

  • イテラブル:

    • iter を適用可能。 __iter__ メソッドを持つ。

  • イテレータ:

    • next を適用可能。 __next__ メソッドを持つ。

    • iter を適用したとき、引数のオブジェクトをそのまま返す。

iter(x)x.__iter__() と等価なので、iter を適用可能であることと、__iter__ メソッドを持つことは同義です。 同様に、next(x)x.__next__() と等価なので、 next を適用可能であることと、__next__ メソッドを持つことは同義です。

__iter__ メソッドと __next__ メソッドについては、6-3で改めて説明します。

練習の解答

[30]:
def last_line(name):
    with open(name, 'r', encoding='utf-8') as f:
        for line in f:
            pass
    return line
[31]:
def last_line(name):
    f = open(name, 'r', encoding='utf-8')
    for line in f:
        pass
    f.close()
    return line
[32]:
def but_first(ls):
    it = iter(ls)
    next(it)
    return it
[ ]: