41
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Python の for ループについて

Last updated at Posted at 2015-09-06

この話のゴール

  • Python の for ループとは何かを知る(わかった気になる)
  • リスト内包表記とジェネレータ表現の違いを知る

for ループ

for_loop.py
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] の要素が順に取り出されているようだが)

てきとうなオブジェクトを入れてみる

実行時エラーを参考にどう叩かれているのか見てみる

duck.py
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 を実装してみる

実装するよう言われたので実装する。ただし空のオブジェクトを返すだけ

duck.py
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回鳴くようにしてみる。

duck.py
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 ループの処理の流れは

  1. iterator を取得
  2. iterator の next() メソッドを呼ぶ
  3. 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]

サンプルコード

list_comprehension.py
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)

サンプルコード

generator_expression.py
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 を返す
41
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?