4-2. イテレータ

イテレータについて簡単に説明します。

参考

next

ファイルオブジェクトは、イテレータと呼ばれるオブジェクトの一種です。 (1-3で説明があったように、Pythonにおける値はオブジェクトと総称されます。) iterateは繰り返すという意味ですよね。 イテレータは、その要素を1つずつ取り出す処理が可能なオブジェクトで、 next という関数でその処理を1回分行うことができます。

変数 f にファイルオブジェクトが入っているとすると、 next(f) は、ファイルから新たに1行を読んで文字列として返します。

[1]:
f = open('sample.txt', 'r')
[2]:
next(f)
[2]:
'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'
[3]:
next(f)
[3]:
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n'
[4]:
next(f)
[4]:
'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'
[5]:
next(f)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_5354/4256387510.py in <module>
----> 1 next(f)

StopIteration:

f をファイルオブジェクトとしたとき、 f.readline()next(f) は、ほぼ同じで、 ファイルから新たに1行を読んで文字列として返します。

ただし、f.readline()next(f) では、ファイルの終わりに来たときの挙動が異なります。 f.readline()'' という空文字列を返すのですが、 next(f)StopIteration というエラーを発します。 以下で説明するfor文は、このエラーを検知しています。 つまり、next(f)StopIteration を発したらforループから抜け出します。

このように、関数 next が適用できて、 next が何らかの値を返すか、StopIteration を発するようなオブジェクトを、 イテレータと呼びます。

[6]:
f.close()

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

一般に、イテレータは、for文の in の後に指定することができます。

したがって、以下のように、ファイルオブジェクトを値とする変数 f を、 for文の in の後に指定することができます。

for line in f:
    ...

繰り返しの各ステップで、next(f) が呼び出されて、 変数 line にその値が設定され、for文の中身が実行されます。

以下の例を見てください。

[7]:
with open('sample.txt', 'r') as f:
    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文で処理をすると、 繰り返し処理がファイルの終わりまで達しているので、 もう一度同じファイルオブジェクトをfor文に与えても何も実行されません。

[8]:
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文を用いて定義してください。

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

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[10]:
print(last_line('sample.txt')=="Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n")
False

iter

いうまでもなく、リスト対してfor文を用いることができますが、リストはイテレータでしょうか。

[11]:
next([1,2,3])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_5354/3204614342.py in <module>
----> 1 next([1,2,3])

TypeError: 'list' object is not an iterator

リストに対して next を適用するとエラーになってしまいます。 したがって、リストはイテレータではありません。

では、なぜリストに対してfor文が適用できるのでしょう。

実は、for文の in の後に指定されたオブジェクトに対しては、 必ず iter という組み込み関数が適用される、という仕掛けになっているのです。

実際に、リストに iter を適用してみましょう。

[12]:
it = iter([1,2,3])

変数 it には、リスト [1,2,3] から作ったイテレータが入っています。

[13]:
it
[13]:
<list_iterator at 0x7fd7a00938e0>
[14]:
next(it)
[14]:
1
[15]:
next(it)
[15]:
2
[16]:
next(it)
[16]:
3
[17]:
next(it)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/tmp/ipykernel_5354/600241529.py in <module>
----> 1 next(it)

StopIteration:

ここで、もう一度イテレータを作り直しましょう。

[18]:
it = iter([1,2,3])

このイテレータに対して以下のようにfor文を用いると、もとのリストの要素が網羅されます。

[19]:
for x in it:
    print(x)
1
2
3

ファイルの場合と同様に、もう一回for文を回しても何も出力されません。

[20]:
for x in it:
    print(x)

もう一度説明すると、for文の in の後に指定されたオブジェクトに対しては、 必ず iter という組み込み関数が適用されて、イテレータが得られます。 そして、そのイテレータに対して next が次々と呼ばれます。

では、for文の in の後にイテレータが指定されるとどうなるでしょうか。 やはり、iter が適用されるのですが、 Pythonでは、イテレータに対して iter が適用されても、 そのイテレータ自身が返ります。

[21]:
with open('sample.txt', 'r') as f:
    print(f is iter(f))
True

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

練習

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

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

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[23]:
it = but_first([0,2,4,6,8])
print(type(it) == type(iter([])))
print(list(it) == [2,4,6,8])
False
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_5354/1844329027.py in <module>
      1 it = but_first([0,2,4,6,8])
      2 print(type(it) == type(iter([])))
----> 3 print(list(it) == [2,4,6,8])

TypeError: 'NoneType' object is not iterable

イテラブル

一般に、関数 iter が適用できるオブジェクトをイテラブルと呼びます。 イテラブルは、for文の in の後に指定することができます。

イテレータに iter を適用すると自分自身が返るので、イテレータはイテラブルでもあります。

リストはイテラブルですが、イテレータではありません。

[24]:
ln = [1,2,3]
for x in ln:
    print(x)
for x in ln:
    print(x)
1
2
3
1
2
3

上の例では、2つのfor文ごとに、lniter が適用されて、別々のイテレータが作られたのです。

関数 range が返すオブジェクトもイテラブルですが、イテレータではありません。

[25]:
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() は、イテレータを返します。

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

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

イテレータもイテラブルなので、enumerate の引数になり得ます。 たとえば、ファイルオブジェクトはイテレータなので、以下のように enumerate の引数になります。

[30]:
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 から始まりますが、各行の行番号と考えられます。

練習の解答

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