進歩しよう...
表をなぞるようなPythonの使い方しかしてなかったのでちょっと調べてみる.
リスト内包
リスト内包(list comprehensions)を使うと変なリストを馬鹿みたいに簡単に作れる.
lst1 = range (10) # これは普通に [0,1,2,...,9] lst2 = [a for a in range (10)] # 上に同じ結果. lst3 = [(a, b) for a in range (3) for b in range (3)] # タプルのリスト[(0,0), (0,1), (0,2), (1,0), .... (2,2)] lst4 = [list ( (a, b) ) for a in range (3) for b in range (3)] # 上のタプルをリスト化
"エクセレント!"
mapやfilterの(場合によるが)便利な代替品も書ける.
lst5 = [a for a in lst2 if a % 2 == 0] # lst2の中から奇数のものを消す.filter的使い方 lst6 = [str (a) for a in lst5] # lst5の各値を数値型から文字列型にキャスト.map的使い方
"エクセレント!!"
イテレータ
イテレータとは数え上げることのできるオブジェクト.
pythonのイテレータはイテレータとして振る舞うオブジェクトを返す__iter__()メソッドをコンテナオブジェクト(list型とか)に実装することと,
そのイテレータオブジェクトはnextメソッドで次の呼び出しとStopItereationのraiseによるイテレーションの停止を定義としている.*1
意味不明なのでコードで表現.
lst = [3, 4, 5] isinstance (a, list) # True lst.__iter__ () # イテレータを作ってみる.ああ,これはlistiterator型なのね.listじゃないよ. <listiterator object at 0x00000> it = lst.__iter__ () # イテレータを変数に束縛してみる. it.next () # 3 it.next () # 4 it.next () # 5 it.next () # except StopIteration
となっている.この仕組みがなんの役に立つのか.それはfor-in文で役に立つ.
for-in文ではinの次ぎにくる値がイテレータとして振る舞うこと,つまりイテレータを返す__iter__メソッドをも実装していることを期待する.
だから,基本的な型であるlist型以外でもイテレータとしての実装が施されていれば同じように動作する訳だ.
class myiter (): def __init__ (self): self.lst = [3, 4, 5] self.index = 0 def __iter__ (self): return self def next (self): if (self.index < len (self.lst)): self.index = self.index + 1 return self.lst[self.index -1] else: raise StopIteration it = myiter () iti = it.__iter__() print iti.next () # 3 print iti.next () # 4 print iti.next () # 5 print iti.next () # except StopIteration for a in myiter (): print a # 0, 1, 2と表示
と,なる.上の例だとmyiterクラス自体をイテレータとして振る舞うようにして,__iter__で自分自身を返すようにしている.
ひとつ,自分の最初勘違いしていた点として,nextメソッドを持つコンテナとイテレータを混同していたこと,
イテレータの定義にはnextメソッドだけではなく,__iter__メソッドも持つことが重要.
コンテナはnextメソッドをもっていようが持っていまいが__iter__が有ればどっちでもいいわけだ.
このような仕組みをプロトコル,言語仕様からくる束縛の様なものとしてpythonは持っているらしい.
あれ,意味不明ですね.このへんはコードを書きまくって理解してください.
ジェネレータ
ジェネレータ(generetor)はイテレータ(iterator)を生成する機構.便利すぎる.
上のmyiterの例はかなり書くのがめんどくさい.
ジェネレータを使えばもっと簡単に書ける.
def genmyiter (): lst = [3, 4, 5] index = 0 while True: if (index != len (lst)): index = index + 1 yield lst [index - 1] else: return it = genmyiter () # コンテナ風味な何かを生成.このオブジェクトもイテーラブルである. ito = it.__iter__() # 明示的にイテレータを生成. print ito.next () print ito.next () print ito.next () print ito.next ()
"エクセレント!!!"
上のgenmyiterは上のmyiterと全く同じ機能を持つが,
ジェネレータはlisp語族でいうところの継続のcall/ccの一般化の様なものである.
継続とは実行時のスナップショットを残したまま処理を一時停止すること.自分的には.
明示的にlambda式でコンテキストの続きを呼び出さないところが違う.
まーそういうことは置いといて,yieldは単純に処理の一旦停止と値の返却を意味する.そしてreturnはイテレーションの終了を意味する.
さらにジェネレータには面白いことがある.ジェネレータは遅延評価:実行時に逐次評価を行うことで計算量を最低限に押さえることができる.
応用としてコルーチン(co-routine)を使った疑似スレッドを書いてみる.
コルーチンを使った処理ではスレッドと違いロック機構がいらない,スレッド切り替えが存在しないなど,小規模な並列処理をくみ上げる場合はこっちの方が早い場合もある.らしいw
class coroutine (): def __init__ (self): self.__lst = [] def __genlooplist (self, lst): while True: iterlst = iter (lst) while True: try: yield iterlst.next () except StopIteration: break def addroutine (self, genobj): self.__lst.append (genobj) def run (self): looplist = self.__genlooplist (self.__lst) for obj in looplist: try: obj.next () except StopIteration: return def generator (name): while True: print name + ' said fuck' yield print name + ' said you' yield cor = coroutine () cor.addroutine (generator ('hoge')) cor.addroutine (generator ('fuga')) cor.addroutine (generator ('pyo')) cor.run () # 結果 hoge said fuck fuga said fuck pyo said fuck hoge said you fuga said you pyo said you hoge said fuck ...
という感じとなる.キューリストとなっているlooplistも循環参照する(ようにみせかけた)イテレータとなっており,
その参照先もイテレータのようになっている.
処理の進み方として,yieldでストップを書けることで複数のタスクが同時進行しているように見えるのが結果よりわかる.
この例だとどれか一つのworker(つめたジェネレータ)がStopIterationを投げると自動停止するけど,
循環リストからworkerを自動で取り除く構造をいれれば,こんなんでも使えるものができそうだ.
ジェネレータ式
さらにジェネレータを進化させたものにジェネレータ式(generator expression)というものがある.
先ほどのリスト内包と同じようにジェネレータを書けるというものだ.
# exp1 generator expression for i in (ge for ge in range (1000) if ge % 2 == 0): print i # exp2 list comprehensions for i in [lc for lc in range (1000) if ic % 2 == 0]: print i
"エクセレント!!!!"
丸括弧が四角括弧になっただけだけど,これはジェネレータとして動く.
exp1のジェネレータ式はfor-in文によってnextメソッドを呼び出されるたびに必要な計算(if文による偶数判定)を行う.
それに対して,exp2のリスト内包は先に偶数のリストを全部作ってから計算を行う.
exp1とexp2の実行速度を比較すると,逐次イテレーションを行うジェネレータ式の方が遅い.
しかし,場合にもよるが途中で処理が終了することが予期される場合は,実行速度とメモリ節約の両観点からジェネレータ式の方が良さそうだ.
まとめ
うーん,pythonはLisp並にパワフルながら可読性が良い.といっても,今回示したものは関数型言語に親しんでいないとなんとも意味不明で読みずらい構文に見えるかもしれないが.