こんにちは!ウメハラ(plumfield56)です。
今回はEffective Pythonの項目7「rangeではなくenumerateを使う」からenumerateに関する一文を補足していきたいと思います。
明確にしたい箇所
本書の中に下記の一文がありました。
enumerateは、遅延評価ジェネレータでイテレータをラップします。
enumerateは、ループのインデックスとイテレータの次の値の対をyeildします。
最初読んだときの感想は「意味がわからなすぎる…」でした。
ということで解読したので1つずつ用語を説明していきたいと思います。
ジェネレータとは?
ジェネレータとはyeild式を使う関数のことです。
またyieldという謎の言葉が出てきました。
一旦、普通の関数との違いをわかりやすく説明するために動きの違いを確認していきます。
ただリストから1つずつ要素を取り出してprintさせるだけの関数です。
def print_num(list):
for num in list:
print(num)
num_list = [1, 2, 3, 4, 5]
print_num(num_list)
# 1
# 2
# 3
# 4
# 5
これにyeildを加えてみます。
yeildを使ってジェネレータを作成した場合はnext関数を使って次の値を取得します。
def print_num(list):
for num in list:
print(num)
yield
num_list = [1, 2, 3, 4, 5]
result = print_num(num_list)
print(result)
next(result)
next(result)
next(result)
# <generator object print_num at 0x************>
# 1
# 2
# 3
関数の中でfor文を書いていますが、yieldが関数ないにあるので戻り値としてジェネレーターが返ってきます。
next関数で実行していきますが、yieldが実行される度に止まり返り値としてyieldで指定した内容を返します。
yieldでの返り値をわかりやすくするためにprintを関数の外に出してあげます。
def print_num(list):
for num in list:
yield num
num_list = [1, 2, 3, 4, 5]
result = print_num(num_list)
print(result)
print(next(result))
print(next(result))
print(next(result))
# <generator object print_num at 0x************>
# 1
# 2
# 3
変更した関数だとyieldで指定した内容が返り値になっているのがわかります。
このように従来は止まることなく処理される内容(for文, while文での処理)が、yieldが入ることによって1つずつ実行される関数のことを遅延評価ジェネレータといいます。
enumerateは遅延評価ジェネレータなのか?
次にenumerateは遅延評価ジェネレータなのかを検証していきたいと思います。
といっても検証は難しくなくてenumerate関数を実行後にnext関数で順番に実行可能かを確かめます。
num_list = [1, 2, 3, 4, 5]
result = enumerate(num_list)
print(result)
print(next(result))
print(next(result))
print(next(result))
# <enumerate object at 0x************>
# (0, 1)
# (1, 2)
# (2, 3)
enumerate関数でイテレータをラップした時の返り値をprintするとジェネレータの時とは違って、enumerate objectを表示されています。
これは特殊メソッドで設定できるのかなと思っていますが、わかっていないのでスルーします。
enumerateは、ループのインデックスとイテレータの次の値の対をyeildします。
インデックスとイテレータの値がタプルでかえって来ているので、この部分も先ほどの検証で理解できると思います。
ラップするってどういうこと?
もうほとんど読み解けていますが、ラップって何かを説明します。
enumerateは、遅延評価ジェネレータでイテレータをラップします。
ラップは包む(wrap)から来ていて、もともとの機能を包んで変換するという動きをします。
先ほどのコードの例ではnum_listというリストをラップしてジェネレータに変換したという意味です。
これで当初目的としていた下記の内容を理解できました。
enumerateは、遅延評価ジェネレータでイテレータをラップします。
enumerateは、ループのインデックスとイテレータの次の値の対をyeildします。
enumerateを作成してみる
enumerateのコードを見たわけではないですが、こんな感じで作られているのかなと思っています。
def my_enumerate(iter, index = 0):
for elem in iter:
yield (index, elem)
index += 1
num_list = [1, 2, 3, 4, 5]
for i in my_enumerate(num_list):
print(i)
# (0, 1)
# (1, 2)
# (2, 3)
# (3, 4)
# (4, 5)
コメント