Pythonのfor文, イテレータ, 内包表記 (Python3対応)

キーワードforつながりで、for文とリスト内包表記について, 突っ込んで解説する。

_for文

Pythonのfor文の構文は、次のようになっている。

for 変数 in オブジェクト:
    繰り返す処理1
    繰り返す処理2
    ...

for文はイテレータ iterator を駆動しているだけ。

オブジェクトは、__iter__ メソッドを持つものであれば, 型はなんでもいい。リストなどのコンテナか、イテレイタオブジェクト、ジェネレイタを与える。これら __iter__()を持つオブジェクトは Python 界隈では iterable と呼ばれる.

__iter__()メソッドはイテレイタを返すように実装する約束。for文は, 最初に, オブジェクト__iter__() メソッドを呼び出し、イテレイタオブジェクトを得る。オブジェクトがコンテナなら新しいイテレイタ、オブジェクトがまさにイテレイタなら、自分自身。

イテレイタは, __next__() メソッドを呼び出す度に、何らかの順序で値を返す。終端に達した場合は StopIteration 例外を投げる。

for文は, イテレイタオブジェクトの__next__()の値を変数に代入し、ブロックの文を実行するのを繰り返す。

例:

Python
  1. words = ['ja', 2, 3.0] # class 'list'
  2. for w in words:
  3. print(w)
実行結果
ja
2
3.0

Pythonでは, for文で変数スコープは導入されない. つまり, for文に入る前に変数が何らかの値を持っていた場合、単に代入により値が変わる。また、for文を抜けた後も、変数を使える。当然、最後に代入された値になっている。

一連の数字を代入して繰り返すには range() を使う。ここで、古い解説では xrange() を使っているページもあるが, xrange() はすでに廃れている。

Python
  1. for num in range(5): # range(5)の値の型は class 'range'
  2. print(num)
実行結果:
0
1
2
3
4

0〜5ではないことに注意。

range()は次のいずれかの引数を取る。

  • range( stop )
  • range( start, stop [, step] )

break文

break文で、ループの途中で脱出できる。

Python
  1. for num in range(10):
  2. print(num)
  3. if num == 5:
  4. break
実行結果:
0
1
2
3
4
5

continue文

continue文で、次の値で、ループのやり直しができる。

Python
  1. for num in range(10):
  2. if num % 2 == 0:
  3. continue
  4. print(num, " 奇数")
実行結果:
1  奇数
3  奇数
5  奇数
7  奇数
9  奇数

二つの値を得る

イテレータが__next__()の戻り値としてタプル tuple を返す場合、for文で複数の変数に値を入れて、ループできる。

Python
  1. colors = ['red', 'greeen', 'blue', 'yellow']
  2. for i, color in enumerate(colors):
  3. print(i, ':', color)
実行結果
0 : red
1 : greeen
2 : blue
3 : yellow

辞書クラスでも、同じことができる。

Python
  1. di = {1:'hoge', 2:'fuga'} # class 'dict'
  2. for k in di:
  3. print(k)
  4. for k, v in di.items(): # di.iteritems() は削除された
  5. print(k, v)
実行結果
1
2
1 hoge
2 fuga

_イテレイタ

for文に与えるオブジェクトは, 型は問わないので、iterable を自作してもよい。

次の例は, ごく簡単な iterable とイテレイタ。二つを兼ねるようにして, クラスを分けずに __iter__() で自分自身を返すようにしてもよい。

なお、イテレイタで定義するのは next() ではない。それはすでに廃れた。

Python
  1. class my_iter:
  2. def __init__(self, v):
  3. self.value = v
  4. def __next__(self): # next()ではない
  5. self.value -= 1
  6. if self.value < 0:
  7. raise StopIteration
  8. return self.value
  9. # 自作の iterable
  10. class count_down:
  11. def __init__(self, v):
  12. self.value = v
  13. def __iter__(self):
  14. return my_iter(self.value)
  15. e = count_down(3)
  16. for i in e:
  17. print(i)
実行結果
2
1
0

逆順イテレータ

もう少し意味のある例として、逆順イテレータを使って、リストを逆に辿ってみる。

Python
  1. list1 = [1, 3, 5, 7, 9]
  2. # 逆順で辿る
  3. for num in reversed(list1): # 型は 'list_reverseiterator'
  4. print(num)
  5. # リストを逆順にする
  6. list_rv = list(reversed(list1))
  7. print(list_rv)
実行結果:
9
7
5
3
1
[9, 7, 5, 3, 1]

イテレータを作ることで、コンテナの要素を様々な形で辿ることができる。

上の例でついでに書いたが、listの構築子 constructor の引数としてイテレータを与えると、そのイテレータの順で新しいリストを作ることができる。

_リスト内包表記

forキーワードが使われる構文として, リスト内包表記 list comprehensions がある。

これは、繰り返しではなくて、新しいリストを生成する。繰り返しじゃない。

Python
  1. expr = []
  2. for i in range(100000):
  3. expr.append(i)

これを、次のように書ける.

Python
  1. expr = [ i for i in range(100000) ]

パッと見、そうとう不思議な構文。for文とはまったく関係がない。基本的な構文は次のようになる

[ for 変数 in オブジェクト ]

リストだけではない

この構文は、リストだけでなく、集合 'set', 辞書 'dict', ジェネレイタの生成にも使える。

セット
{ for 変数 in オブジェクト }
辞書
{ キー式: 値式 for k,v in オブジェクト }
ジェネレイタ
( for 変数 in オブジェクト )

リスト、セット、辞書の内包表記は、この式を評価したときに, すべて展開される。リストを作りきってしまう。

もし,「リスト」が欲しいのではなく、要素の値が順番に得られれば十分なのなら、ジェネレイタを作るようにすべき。ジェネレイタの場合は、この式を評価したときには、実行されない。

条件により要素を生成しない

forで生成した系列のうち、条件に合う場合のみ要素を生成する。

if 条件式」までが一式。これも、if文とは関係なく, 特別な構文になっている。後置ifの構文ではない.

Python
  1. list2 = [ i for i in range(10) if i % 2 == 0 ] # range()の後ろにコロン不要
  2. print( list2 )
実行結果
[0, 2, 4, 6, 8]

要素の生成式を選ぶ

飛び飛びではなく, 要素を生成するが, その値の生成式を変えたい場合は次のように書く。

[ then節の式 if 条件式 else 式 for文 ] となる。ifがあっち行ったりこっち行ったり。

Python
  1. list3 = [ i if i % 2 == 0 else str(i * 100) for i in range(10) ]
  2. print( list3 )
実行結果
[0, '100', 2, '300', 4, '500', 6, '700', 8, '900']

これは後置ifの構文に似ている。ただし、Pythonのifは式ではなく文なので、後置ifそのものではない。

同じことをするのに, 後置ifを使えば次のように書き換えられる.

Python
  1. list3 = []
  2. for i in range(10):
  3. list3.append(i) if i % 2 == 0 else list3.append(str(i * 100))
  4. print( list3 )

ネストしたfor

リスト内包表記のforは複数書ける。右側に書いたものが内側のループになる。ifも使える。

Python
  1. # 右のfor が内側
  2. list1 = [ (x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y ]
  3. print( list1 )
  4. # 右のfor では左側の変数を参照できる
  5. vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  6. list2 = [ num for elem in vec for num in elem ]
  7. print( list2 )
実行結果:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]