この話のゴール
- Python の for ループとは何かを知る(わかった気になる)
- リスト内包表記とジェネレータ表現の違いを知る
for ループ
for i in [0, 1, 2, 3, 4]:
print(i)
実行すると
% python for_loop.py
0
1
2
3
4
for i in
の後ろのモノがどう使われているかを知りたい。
(上の例の場合、何となく [0, 1, 2, 3, 4]
の要素が順に取り出されているようだが)
てきとうなオブジェクトを入れてみる
実行時エラーを参考にどう叩かれているのか見てみる
class Duck(object):
pass
if __name__ == '__main__':
for i in Duck():
print(i)
実行すると iterable じゃないと怒られる
% python duck.py
Traceback (most recent call last):
File "duck.py", line 6, in <module>
for i in Duck():
TypeError: 'Duck' object is not iterable
- iterable とは
__iter__()
メソッドを持つオブジェクトである - そういう定義なのでひとまず信じる
iterable を実装してみる
実装するよう言われたので実装する。ただし空のオブジェクトを返すだけ
class DuckIter(object):
def __init__(self):
pass
class Duck(object):
def __iter__(self):
return DuckIter()
if __name__ == '__main__':
for i in Duck():
print(i)
エラーが変わった(一歩前進!)
% python duck.py
Traceback (most recent call last):
File "duck.py", line 12, in <module>
for i in Duck():
TypeError: iter() returned non-iterator of type 'DuckIter'
- iterator とは
next()
メソッドを持つオブジェクトである - Python3 の場合は
__next__()
メソッド
iterator を実装してみる
ガーガーと3回鳴くようにしてみる。
class DuckIter(object):
def __init__(self):
self._count = 3
def next(self):
if self._count > 0:
self._count -= 1
return "quack"
raise StopIteration()
class Duck(object):
def __iter__(self):
return DuckIter()
if __name__ == '__main__':
for i in Duck():
print(i)
実行してもエラーはもう出ない
% python duck.py
quack
quack
quack
アヒルが鳴いた!
ダックタイピング
If it walks like a duck and quacks like a duck, it must be a duck
https://ja.wikipedia.org/wiki/ダック・タイピング
オブジェクトに何ができるかはオブジェクトそのものが決定する
(それらがどのような継承関係を持っているかとは無関係に)
先ほどの例の場合、
- Duck:
__iter__()
メソッドを持っているので iterable と見做された。 - DuckIter:
next()
メソッドを持っているので iterator と見做された。
これにより、for ループを回すことができた:
for i in Duck():
print(i)
この表記は以下とだいたい同じ
iter = Duck().__iter__()
while True:
try:
i = iter.next()
print(i)
except StopIteration:
break
つまり、for ループの処理の流れは
- iterator を取得
- iterator の next() メソッドを呼ぶ
- StopIteration でなければ「ループの中の処理」をして再び 2. へ
ちょっと練習
list から、逆順の iterator を持つオブジェクトを作ってみよう
>>> lst = [0, 1, 2, 3, 4]
>>> rev = RevList(lst)
>>> for r in rev:
... print(r)
...
4
3
2
1
0
ヒント:
-
__iter__()
で「next()
メソッドを持つオブジェクト」を取得できるように -
next()
で元のリストの末尾から順に返す
解答1
- 素直にヒントに従う
- 元のリストをコピーせず、そのまま参照
class RevListIter(object):
def __init__(self, lst):
self._orig = lst
self._i = len(lst)
def next(self):
if self._i > 0:
self._i -= 1
return self._orig[self._i]
raise StopIteration()
class RevList(object):
def __init__(self, lst):
self._orig = lst
def __iter__(self):
return RevListIter(self._orig)
if __name__ == '__main__':
lst = [0, 1, 2, 3, 4]
rev = RevList(lst)
for r in rev:
print(r)
解答2
- step=-1のスライスで逆順のリストを作る
- そのリストの iterator を返す
class RevList(object):
def __init__(self, lst):
self._orig = lst
def __iter__(self):
return self._orig[::-1].__iter__()
if __name__ == '__main__':
lst = [0, 1, 2, 3, 4]
rev = RevList(lst)
for r in rev:
print(r)
2つの解答の違い
- 解答1は iterator を作る時ではなく、使う時に都度要素を生成している
- 解答2は iterator を作る時に逆順のリストを生成している
リスト内包表記とジェネレータ表現
もう一つ練習
整数のリストの各要素を二乗したイテレータをつくろう
>>> lst = [0, 1, 2, 3, 4]
>>> for i in なにか(lst):
... print(i)
...
0
1
4
9
16
2つの解答例
>>> lst = [0, 1, 2, 3, 4]
>>> for i in [x*x for x in lst]:
... print(i)
...
0
1
4
9
16
>>> for i in (x*x for x in lst):
... print(i)
...
0
1
4
9
16
どちらも解になっている。2つの違いを見ていこう。
リスト内包表記
記法
[f(x) for x in lst]
サンプルコード
def f(x):
print("%d*%d" % (x, x))
return x*x
if __name__ == '__main__':
lst = [0, 1, 2, 3, 4]
x = [f(x) for x in lst]
print(type(x))
for i in x:
print(i)
実行結果
% python list_comprehension.py
0*0
1*1
2*2
3*3
4*4
<type 'list'>
0
1
4
9
16
- list を返す
- for ループよりも前に計算して、結果のリストを生成
- for ループでは値を順に取り出すだけ
ジェネレータ表現
記法
(f(x) for x in lst)
サンプルコード
def f(x):
print("%d*%d" % (x, x))
return x*x
if __name__ == '__main__':
lst = [0, 1, 2, 3, 4]
x = (f(x) for x in lst)
print(type(x))
for i in x:
print(i)
実行結果
% python generator_expression.py
<type 'generator'>
0*0
0
1*1
1
2*2
4
3*3
9
4*4
16
- generator を返す
- for ループで使うより前には計算してない
- for ループの中で、値を取り出す際に都度計算
まとめ
- Python は duck typing である
-
__iter__()
,next()
を実装することで、iterable が出来上がる - Python3 では
__next()__
- for ループは、 iterator から順に値を取り出して処理を回す
- listからiterableを作る記法
- list comprehension: 作る時に計算して結果のlistを生成
- generator expression: 作る時に計算しない。使う時に都度計算
- 関連: map 関数
- Python2 ではlist comprehensionに相当→list を返す
- Python3 ではgenerator expression に相当→iterator を返す