反復子
しばしばデータ分析の重要な部分は、自動化の流れの中で、繰り返し、よく似た計算を繰り返すことです。例えば、最初と最後に分割したい名前の表、またはいくつかの標準形に変換したい大方の日付データをもつかもしれないです。これに対するPythonの答えの一つはiteratorシンタックスです。range反復子ですでにこれをみてきました。
In [1]:
for i in range(10):
print(i, end=' ')
0 1 2 3 4 5 6 7 8 9
ここで、ちょっと深く掘り下げます。Python3ではrangeはリストでなく反復子と呼ばれる何かとわかります、そして、それがいかに動作するかを学ぶことが、Pythonのとても使いやすい機能性をもつ幅広いクラスを理解する手がかりであることがわかります。
リストを渡る反復
反復子は、リストを通して反復する具体的な場合で、多分たいてい使いやすく理解されます。次を考えましょう:
In [2]:
for value in [2, 4, 6, 8, 10]:
# do some operation
print(value + 1, end=' ')
3 5 7 9 11
ありふれた"for x in y"シンタックスはリストの中の各値に対するいくつかの操作を繰り返せます。コードの文法が英語の記述("for [each] value in [the] list")にとても近いという事実は、学んで使うのにそんな直感的な言語にPythonをさせる、ちょうど文法的な選択の一つになってます。
しかし、そのままの振る舞いは、本当に何が起きるかということではないです。"for val in L"のようなものを書くとき、Pythonのインタプリタはそれが反復子のインタフェースを持つかどうかを調べます、それは組み込みのiter関数であなたがあなた自身をチェックするものです:
In [3]:
iter([2, 4, 6, 8, 10])
Out[3]:
<list_iterator at 0x104722400>
forループによって要求される機能を供給することがこのiteratorオブジェクトです。iterオブジェクトは、有効になるのと同じくらい長くまで、次のオブジェクトへアクセスすることを与えるコンテナです、それは次に組み込みの関数でみられます:
In [4]:
I = iter([2, 4, 6, 8, 10])
In [5]:
print(next(I))
2
In [6]:
print(next(I))
4
In [7]:
print(next(I))
6
この間接的な段階の目的はなんでしょうか?そうです、これは信じられないくらい役立つとわかります、なぜなら、Pythonは実際にリストでないものをリストとして扱うことを可能とするのです。
range(): リストはいつもリストではない
多分たいていの一般的なこの間接反復の例は、Python3のrange()関数です(Python2でxrange()という名前です)、それはリストでなく特別なrange()オブジェクトを返します。
In [8]:
range(10)
Out[8]:
range(0, 10)
rangeはlistのように反復子を表出します。
In [9]:
iter(range(10))
Out[9]:
<range_iterator at 0x1045a1810>
そんな感じでPythonはまるでリストであるかようにそれを扱うことをわかっています。
In [10]:
for i in range(10):
print(i, end=' ')
0 1 2 3 4 5 6 7 8 9
イテレータの間接参照の利便性は全リストが決して明示的に作られないということです。もし本当にそれをインスタンス化するなら(Python2では、rangeはリストを作ることに留意してみください、つまり次を実行することがよいことではないのです)メモリを圧倒するrangeの計算を行うことによりこれがわかります:
In [11]:
N = 10 ** 12
for i in range(N):
if i >= 10: break
print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
もしrangeが実際に一兆の値のリストを作るなら、メモリについて10テラバイトを占めることになります:最初の10の値以外は全部無視する場合があったら無駄です。
事実上、反復子が最後まで到達する理由がないですね。Pythonのitertools ライブラリは、無限のrangeとして実行するcount関数を含んでいます。
In [12]:
from itertools import count
for i in count():
if i >= 10:
break
print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
ここでループのbreakを中にいれないなら、プロセスが手動で中断もしくはkillされるまで(例えばctrl-Cを使って)楽しく数えつづけるでしょう。
役に立つ反復子
このiteratorシンタックスは、後の節で探求するであろうより多いデータサイエンスに特化したオブジェクトと同じように、ほとんどのPythonの組み込み型で全般的に使われます。ここで、Python言語におけるより使いやすいイテレータのいくつかを網羅します:
enumerate
しばしば配列の値だけでなく、インデックスの走査を反復する必要があります。このようにしたい誘惑に狩られるかもしれません:
In [13]:
L = [2, 4, 6, 8, 10]
for i in range(len(L)):
print(i, L[i])
0 2
1 4
2 6
3 8
4 10
これは動作しますが、Pythonはenumerateイテレータを使って、より明らかなシンタックスを提供します:
In [14]:
for i, val in enumerate(L):
print(i, val)
0 2
1 4
2 6
3 8
4 10
これはよりPython的な方法で、リスト中の索引と値を数え上げます。
zip
他のときで、同時に一通り反復したいリストを二重にしたいかもしれません。Pythonぽくない例で前にみたように、確かにインデックスを反復しきることができますが、zipイテレータを使うことがよりよいです、それは一緒に反復可能でジップします:
In [15]:
L = [2, 4, 6, 8, 10]
R = [3, 6, 9, 12, 15]
for lval, rval in zip(L, R):
print(lval, rval)
2 3
4 6
6 9
8 12
10 15
反復できる任意の数は共にまとめられます、そしてそれらは異なる長さなら、最も短いのがzipのサイズとなるでしょう。
map と filter
mapイテレータは、関数をとり、それをイテレータの中で値に適用します。
In [16]:
# find the first 10 square numbers
square = lambda x: x ** 2
for val in map(square, range(10)):
print(val, end=' ')
0 1 4 9 16 25 36 49 64 81
filterイテレータは、filter関数がTrueと評価する値を唯一通過することをのぞいて、似たようなものです:
In [17]:
# find values up to 10 for which x % 2 is zero
is_even = lambda x: x % 2 == 0
for val in filter(is_even, range(10)):
print(val, end=' ')
0 2 4 6 8
reduce関数(Pythonのfunctoolsモジュール)のように、mapとfilter関数は関数プログラミングの形式で基本となる構成物で、それはPythonの世界における支配的なプログラミングスタイルではない一方で、率直な提案者を持つものです(例えば、pythoolsライブラリをみましょう)。
関数の引数としてのイテレータ
argsと**kwargsの中で、柔軟な引数をみました。そのargsと**kwargsは関数に対してシーケンスと辞書を通るのに使われます。*argsシンタックスはシーケンスだけでなくどんなイテレータでも動作することがわかります:
In [18]:
print(*range(10))
0 1 2 3 4 5 6 7 8 9
そう、例えば、前から次のにmapの例を巧妙にとって圧縮できます:
In [19]:
print(*map(lambda x: x ** 2, range(10)))
0 1 4 9 16 25 36 49 64 81
この仕掛けを使うことは、Pythonの学習者のフォーラムで生じる年齢問題に答えさせます。zip()に対してなぜunzip()関数がないんですか?もし全体としてそれについて考えて暗いクローゼットであなた自信を閉じるなら、zip()の反対はzip()だということを悟るかもしれない。手がかりはzip()はイテレータやシーケンスの任意の数を一緒にzipできるということです。みましょう:
In [20]:
L1 = (1, 2, 3, 4)
L2 = ('a', 'b', 'c', 'd')
In [21]:
z = zip(L1, L2)
print(*z)
(1, 'a') (2, 'b') (3, 'c') (4, 'd')
In [22]:
z = zip(L1, L2)
new_L1, new_L2 = zip(*z)
print(new_L1, new_L2)
(1, 2, 3, 4) ('a', 'b', 'c', 'd')
しばらくこれを熟考しましょう。もしなぜそれが動作するかを理解するなら、Pythonのイテレータを理解する点で、 大いに役立つこととなるでしょう。
特別なイテレータ: itertools
私たちは簡単に、無限のrangeイテレータ、itertools.count をみました。itertoolsモジュールはたくさんの便利なイテレータを含んでいます。利用できるものを見るためにモジュールを探求することはあなたの中でよい価値となります。例として、itertools.permutations関数を考えてみましょう、これはシーケンスのすべての順列を反復します:
In [23]:
from itertools import permutations
p = permutations(range(3))
print(*p)
(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)
Similarly, the itertools.combinations function iterates over all unique combinations of N values within a list:
In [24]:
from itertools import combinations
c = combinations(range(4), 2)
print(*c)
(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)
関連する何かはproductイテレータです、それは二つかそれ以上の反復可能なもの間の全てのペア集合を反復しきります:
In [25]:
from itertools import product
p = product('ab', range(3))
print(*p)
('a', 0) ('a', 1) ('a', 2) ('b', 0) ('b', 1) ('b', 2)
多くのより使いやすいイテレータがitertoolsに存在します、完全なリストはいくつかの例をそえて、Pythonのオンラインドキュメントでみられます。