イテレータ関連の処理
先に説明したように、Pythonでは繰り返し処理にイテレータを用います。イテレータが言語仕様に盛り込まれていることもあり、ライブラリや関数の戻り値など、至る所でイテレータが登場してきます。ここでは、イテレータに関わる処理で面白い部分について見ていきます。
リストを、mapとfilterで変換する
まずは、map
とfilter
を紹介します。
map
は、リスト内の要素を変換し、新しいリストを作って返します。他の言語のmap
を知っていれば、おなじみの動作です。Pythonは全てがオブジェクトとして扱われますので、組み込み関数であっても通常のオブジェクトとして引数に渡せます。また、メソッドも適用することができます。
>>> # len() 関数を適用して文字列長を調べる >>> map(len, ["Python", "Perl", "Java", "Haskell"]) [6, 4, 4, 7] >>> # 文字列型のupper()メソッドにより、大文字にする >>> map(str.upper, ["Python", "Perl", "Java", "Haskell"]) ['PYTHON', 'PERL', 'JAVA', 'HASKELL']
もちろん、ユーザが定義した関数も適用できます。ここでは、lambda
文を利用して生成した無名関数を適用させてみます。lambda
文は、lambda 引数: 計算式
、という記法で関数を表します。
>>> import math # 数学関数のimport
>>> map(lambda (a, b): math.sqrt(a ** 2 + b ** 2), [(3, 4), (0, -2), (-1, -1)])
[5.0, 2.0, 1.4142135623730951]
さて、次はfilter
です。これはPerlではgrep
関数に相当します。引数はmap
と同様に関数とリストですが、渡した関数が真になる要素だけからなるリストを返します。
# アルファベットと数字だけからなるものだけを取り出す >>> filter(str.isalnum, ["100$", "abc123", "dyndns.hiratara.org"]) ['abc123']
map
とfilter
を入れ子にして組み合わせることもできます。
>>> map(lambda a: a ** 2, filter(lambda a: a > 0, [-1, 2, -3, 4, -5, 6])) [4, 16, 36]
map、filter系の処理を簡潔に書く
map
とfilter
を用いる処理は、多くの場面で利用されます。そのため、これらの処理を直感的に利用出来るような記述法が用意されています。それが、内包表記と呼ばれるものです。
さっきの処理を内包表記で書くと、以下のようになります。
>>> [ a ** 2 for a in [-1, 2, -3, 4, -5, 6] if a > 0 ] [4, 16, 36]
余分な関数記述が消え、ずいぶんとすっきりした感じになりました。for ~ in ~
の形で対象となる要素一覧を表し、if ~
でfilter
をかけます。一番左に書いた式( a ** 2 )
でmap
し、表示します。
このfor
文は入れ子にできます。3つのリストの直積を取るような内包表記は、以下のようになります。
>>> [(x, y, z) for x in range(3) for y in range(3) for z in range (3)] [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (0, 2, 1), (0, 2, 2), (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 2, 0), (1, 2, 1), (1, 2, 2), (2, 0, 0), (2, 0, 1), (2, 0, 2), (2, 1, 0), (2, 1, 1), (2, 1, 2), (2, 2, 0), (2, 2, 1), (2, 2, 2)]
さらに、map
やfilter
の場合に渡す関数は、引数が1個である必要がありましたが、内包表記ではそのような制限がありません。
>>> # 母音を全て削除する >>> import re >>> [re.sub(r'[aiueo]', r'', s) for s in ["Python", "Perl", "Java", "Haskell"]] ['Pythn', 'Prl', 'Jv', 'Hskll']
ここで出てきたreは、正規表現モジュールです。re.sub(pattern, repl, string)
は、string
内のpattern
をrepl
で置換するコマンドです。これにより、リスト内の文字列の全てを置換したリストを作っています。
また、r'文字列'
の形式は、raw stringと呼ばれるもので、普通の文字列と違って\nなどのエスケープシーケンスが展開されません。正規表現を記述する場合にはエスケープシーケンスが展開されない方が便利なので、この記述を使うことが多いです。
enumerate()とzip()
さらに、かゆいところに手が届く関数を2つ紹介します。
まず、enumerate()
です。イテレータ処理時に、ループ内で連番が欲しくなって自分でカウンターを作ってインクリメントするというのはよくある話ですが、これはその処理をやってくれます。リストへenumerate()
を噛ませると、リストの値は、(連番, リストの値)
という要素に変換されます。カウンター処理と要素の取り出し処理が両方同時にでき、すっきりした記述が可能です。
>>> # リストに番号をつけて表示する >>> for i, item in enumerate(['Python', 'Perl', 'Java', 'Haskell']): ... print "%d: %s" % (i + 1, item) ... 1: Python 2: Perl 3: Java 4: Haskell
次に、zip()
関数です。この関数を使うと、複数のリストから同時に値を取り出すことができます。zip()
関数にlist1とlist2を与えると、ループごとにそれぞれのリストから値が一つずつ返ってきます。例えば、2つのリストに辞書型のキーと値にしたいような値が含まれているときは、この関数を使うと便利です。
>>> names = ['Takeshi', 'Satoshi', 'Yoshio', 'Kazuki'] >>> ages = [ 22, 30, 28, 32] >>> for name, age in zip(names, ages): ... print "%s は %d 歳です" % (name, age) ... Takeshi は 22 歳です。 Satoshi は 30 歳です。 Yoshio は 28 歳です。 Kazuki は 32 歳です。
for
でカウンタ変数を使わないPythonにとって、これらの関数が可読性の高いコードを書くためにとても有効なツールとなっています。
イテレータを作る
ここまで、リストを有効に利用するための内包表記や各関数について紹介してきましたが、これらの事項はイテレータに対しても適応できます。イテレータは、繰り返し処理の開始時にはどのくらい繰り返すのか決定しにくい(できない)ような処理で大きな威力を発揮します。ファイルの行単位での読み込み処理がいい例です。ファイルを最後まで読まない限りファイルが何行あるのかはわからないため、ループ開始時には何回処理を繰り返さなければならないのかがはっきりしません。このような場合に、イテレータが活躍します。
さて、Pythonでイテレータとは、イテレータプロトコルに準拠したオブジェクトのことです。このプロトコルに準拠さえすれば、どのようなオブジェクトもイテレータとなり得ます。しかし、例えば、素数を返すイテレータ作るために、いちいちイテレータプロトコルに準拠したクラスを書くのは面倒なことです。
そこで、もっと直感的にイテレータを生成する方法が用意されています。それが、ジェネレータ関数という仕組みです。ジェネレータ関数は普通の関数と同じような記述で宣言をしますが、return
の代わりにyeild
という句を使います。yeild
は値を関数の外へ返しますが、この時にreturn
と違って関数の内部状態を維持します。そして、次にまた値が必要とされた時には、前回yield
した続きから処理が始まります。
ここでは、例として1が出るまでサイコロを降り続けるイテレータを作成してみます。サンプルコードで利用しているrandomモジュールは名前の通り乱数のモジュールで、さいの目を作成するのに使っています。
>>> # 1 が出るまでサイコロの目を返し続けるイテレータ >>> import random >>> def throw_dice(): ... n = 0 ... while n != 1: ... n = random.randint(1, 6) ... yield n ... >>> # さいころをふってみる >>> for dice in throw_dice(): ... print "さいころの目は、 %d でした" % dice ... さいころの目は、 2 でした さいころの目は、 3 でした さいころの目は、 6 でした さいころの目は、 6 でした さいころの目は、 1 でした
throw_dice()
関数の戻り値がイテレータになっていることがわかるでしょうか? yield
を使うだけで、こんなに簡単にイテレータが作れてしまいます。
当然、このイテレータにmap()
を適用することもできます。
>>> # さいころの目を二乗にする
>>> map( lambda n: n ** 2, throw_dice() )
[9, 16, 16, 4, 1]
また、内包表記でイテレータを作ることもできます。リスト内包表記では先に説明したように大括弧[]を使いましたが、これを丸括弧()に変えると、リストではなくイテレータが生成されます。()による内包表記はジェネレータ式と呼ばれ、リストの内包表記とは違って式が遅延評価されます。扱う要素が多い場合は、()によるジェネレータ式の方が有利です。
>>> # さいころの目を二乗にする >>> for n in (dice ** 2 for dice in throw_dice()): ... print n ... 4 16 9 9 4 1
このように、Pythonではイテレータを簡単に作成する手段と、それを至る所で利用できるようにする仕組みが整っています。ここまでに述べた処理を使いこなせば、一つ間違えば単調になりがちな繰り返し処理を、ダイナミックかつスマートに記述することができます。