スコープ
for文でループ毎にスコープが生成されないのを忘れてループ内でループ変数を参照するクロージャを作ろうとしてハマッた.
※本当はリスト内包表現などを使うべきだが,元のコードはもっと長くて見づらくなってしまうのでこういう書き方をしていた.あとリスト内包表現でも今回の問題は起こる.
>>> xs = [] >>> for x in range(10): xs.append(x) >>> xs [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
だけど,
>>> xs = [] >>> for i in range(10): xs.append(lambda: i) >>> [f() for f in xs] [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
こうなる.
Schemeとか使っているせいだろうか.
ちなみにC#はfor文の中はスコープ生成されますが,ループ変数はループの外側のスコープに定義されるという間を取ったような(?)仕様です.
この辺の扱いは他の言語も調べてみると面白いのかもしれない.
こうすると毎回外側のlambdaのコールフレームでyにxの値が束縛されるので回避できる.
>>> xs = [] >>> for x in range(10): xs.append((lambda y: lambda: y)(x)) >>> [f() for f in xs] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Python3000ではリスト内包表現内の変数がローカルになるらしいのだが,こっちはこのままだった気がする.
SimpleAPI を使ってWikipedia を検索する
ちょっとWikipediaを検索する機能が必要になったけど,
ちょっと探した感じではなかったので http://wikipedia.simpleapi.net/ を利用して書いて見た.
import urllib import httplib from xml.dom import minidom _API_HOST = "wikipedia.simpleapi.net" _API_PATH = "/api" _RESULT_ELEMS = ('language', 'id', 'url', 'title', 'body', 'length', 'redirect', 'strict', 'datetime') def search(keyword): """ Search Wikipedia for <keyword>. The results are returned as a list of dictionaries. Each dictionary represents a result of search results. """ def _get_text(nodes): """Get text node in the nodelist""" texts = [n.data for n in nodes if n.nodeType == n.TEXT_NODE] return ''.join(texts) params = urllib.urlencode({"keyword": keyword}) headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/xml"} conn = httplib.HTTPConnection(_API_HOST) conn.request("POST", _API_PATH, params, headers) response = conn.getresponse() if response.status == 200: data = response.read() dom = minidom.parseString(data) ret = [] for result in dom.getElementsByTagName('result'): d = {} for e in _RESULT_ELEMS: d[e] = _get_text(result.getElementsByTagName(e)[0].childNodes) ret.append(d) return ret else: return None if __name__ == '__main__': result = search("TCP/IP") for r in result: print r['title'] for k,v in r.items(): if k != 'title': print "\t%(k)s:\t%
結果はマッチした結果一つを一つの辞書として,全てのマッチ結果の配列として返ります.
作ったはいいものの,やりたいことをやるには英語版なども検索しなければならないので用が足らないのであった.
WikipediaをMechanizeで使おうとすると拒否されてしまうし...
ちょっとした用途なので無視しちゃっても良さそうだけど,なんとなく気が引ける.
アーカイブを落としてきて使うしかないのかな.
先日のCythonモジュールの速度を調べる
で,実際どれくらい早いの?ということで計測してみる.
比較用の Pure Python なコードは以下.
なるべくオブジェクト割り当ての総量が減るようにちょっとだけ修正したつもり.
pyprimes.py
def primes(kmax): result = [] if kmax > 1000: kmax = 1000 p = [0]*kmax k = 0 n = 2 i = 0 while k < kmax: i = 0 while i < k and n % p[i] != 0: i = i + 1 if i == k: p[k] = n k = k + 1 result.append(n) n = n + 1 return result
timeit モジュールを使って計測コードは以下.
timeit は初めて使ったので色々と間違っているかも.
import timeit import primes import pyprimes cy_setup = ''' from __main__ import primes ''' py_setup = ''' from __main__ import pyprimes as primes ''' stmt10 = ''' primes.primes(10) ''' stmt100 = ''' primes.primes(100) ''' cy_to10_10000times = timeit.Timer(stmt=stmt10, setup=cy_setup).timeit(number=10000) py_to10_10000times = timeit.Timer(stmt=stmt10, setup=py_setup).timeit(number=10000) cy_to100_10times = timeit.Timer(stmt=stmt100, setup=cy_setup).timeit(number=10) py_to100_10times = timeit.Timer(stmt=stmt100, setup=py_setup).timeit(number=10) print 'cython: to10x10000=%f to100x10=%f' % (cy_to10_10000times, cy_to100_10times) print 'python: to10x10000=%f to100x10=%f' % (py_to10_10000times, py_to100_10times)
結果はこんな以下@P4 3GHz.
cython: to10x10000=0.066124 to100x10=0.001934 python: to10x10000=0.384062 to100x10=0.019198
計測したケースが少ないので,どんな場合もこれくらい差がでるとはいえませんが相当速いのは間違いないようです.
CythonをWindows XP + MinGWで使うメモ
Pythonの拡張を作る方法は世に数多くあるが,休み時間にふと思い立ってWindowsでCython(http://www.cython.org/)を使おうと思った時のメモ.
CythonはPyrex(http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/)の後継と思しき言語(?)で,Sage(http://wiki.sagemath.org/)というPythonを使った数学ソフトウェアの開発のために作られたらしい.
Pyrexとの違いはhttp://wiki.cython.org/DifferencesFromPyrex を参照のこと.
Pyrexからの乗り換え組みがそこそこいるらしい.今から使うならCythonを選択すべきなのかもしれない.
実際には機能強化点以外はほとんどPyrexであり,実際公式wikiのチュートリアルとしてPyrexのチュートリアルが紹介されていたりする.Pyrex自体日本語情報が少ないのだが,Cythonにいたっては英語情報も実際に使っているソースコード以外あまり無いので,最小構成のサンプルが欲しかった自分は少々苦労する羽目になった.
以下環境はWindows XP + Python2.5 + MinGW + MSYSを想定.
インストール
公式サイトから最新のソースをダウンロードしてきて展開し
python setup.py install
で普通にインストールできる.
使ってみる
サンプルコード
サンプルとしてPyrexのサイトにある指定数の素数を返す関数primesを使う.
ソースコードは以下.
def primes(int kmax): cdef int n, k, i cdef int p[1000] result = [] if kmax > 1000: kmax = 1000 k = 0 n = 2 while k < kmax: i = 0 while i < k and n % p[i] <> 0: i = i + 1 if i == k: p[k] = n k = k + 1 result.append(n) n = n + 1 return result
ほとんどPythonなのだが関数定義の引数や変数に型を指定できるようになっていて,高速化を計ることができるようだ.
これをprimes.pyxとして適当な作業フォルダに保存する.
Cコードへのコンパイル
先ほどの作業フォルダで
python $PYTHON_ROOT_PATH/Scripts/cython.py primes.pyx
でCソースコードprimes.cが生成される.
共有ライブラリのビルド
同様に作業フォルダでMSYSのbashから共有ライブラリをビルドする.
参考にしたページの関係で何かいらないオプションが指定されていたりするかもしれないが気にしてはいけない.
gcc -c -I$PYTHON_ROOT_PATH/include primes.c dlltool -A --dllname primes.pyd --output-exp primes.exp --output-lib primes.lib primes.o gcc -shared -o primes.pyd primes.o primes.exp -L$PYTHON_ROOT_PATH/libs -lpython25
使う
Pythonのモジュール検索パスの通ったところに置き普通にimportしてやれば使える.(拡張子をpydにしないといけないことに気づかずしばらくハマっていた.)
>>> import primes >>> primes.primes(10) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
第3回1000spekaersに参加して発表してきたことへの報告
Pygletで遊ぼう
わずかな時間を見つけて Pyglet(http://www.pyglet.org/) で遊んだ。
立方体が回ります。
from __future__ import with_statement from contextlib import contextmanager import ctypes from pyglet import gl from pyglet import window @contextmanager def gl_excursion(): gl.glPushMatrix() yield gl.glPopMatrix() @contextmanager def gl_mode(op): gl.glBegin(op) yield gl.glEnd() def set_view(width, height): gl.glViewport(0, 0, width, height) gl.glMatrixMode(gl.GL_PROJECTION) gl.glLoadIdentity() gl.gluPerspective(65, width / float(height), 0.01, 1000) gl.gluLookAt(2, 2, 2, 0, 0, 0, 0, 1, 0) gl.glMatrixMode(gl.GL_MODELVIEW) def on_resize(width, height): set_view(width, height) class Box(object): def __init__(self, size=(1, 1, 1)): self._size = size def draw(self): lx,ly,lz = map(lambda x: 0.5 * x, self._size) gl.glColor4f(0.5, 0.5, 0.5, 0.5) with gl_excursion(): # Front gl.glNormal3f(0, 0, 1) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(lx, ly, lz) gl.glVertex3d(-lx, ly, lz) gl.glVertex3d(lx, -ly, lz) gl.glVertex3d(-lx, -ly, lz) # Back gl.glNormal3f(0, 0, -1) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(lx, ly, -lz) gl.glVertex3d(lx, -ly, -lz) gl.glVertex3d(-lx, ly, -lz) gl.glVertex3d(-lx, -ly, -lz) # Left gl.glNormal3f(-1, 0, 0) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(-lx, ly, lz) gl.glVertex3d(-lx, ly, -lz) gl.glVertex3d(-lx, -ly, lz) gl.glVertex3d(-lx, -ly, -lz) # Right gl.glNormal3f(1, 0, 0) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(lx, ly, lz) gl.glVertex3d(lx, -ly, lz) gl.glVertex3d(lx, ly, -lz) gl.glVertex3d(lx, -ly, -lz) # Top gl.glNormal3f(0, 1, 0) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(lx, ly, lz) gl.glVertex3d(lx, ly, -lz) gl.glVertex3d(-lx, ly, lz) gl.glVertex3d(-lx, ly, -lz) # Bottom gl.glNormal3f(0, -1, 0) with gl_mode(gl.GL_TRIANGLE_STRIP): gl.glVertex3d(lx, -ly, lz) gl.glVertex3d(-lx, -ly, lz) gl.glVertex3d(lx, -ly, -lz) gl.glVertex3d(-lx, -ly, -lz) def _main(): config = gl.Config() config.double_buffer = True win = window.Window( config=config, width=320, height=240, visible=False, resizable=True) win.on_resize = on_resize box = Box() gl.glClearColor(0.5, 0.5, 1, 1) gl.glEnable(gl.GL_LIGHTING) gl.glEnable(gl.GL_LIGHT0) gl.glEnable(gl.GL_DEPTH_TEST) gl.glEnable(gl.GL_CULL_FACE) gl.glCullFace(gl.GL_BACK) Vector4f = ctypes.c_float * 4 light_pos = Vector4f(3.0, 5.0, 5.0, 0.0) light_dif = Vector4f(1.0, 1.0, 1.0, 1.0) gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, light_pos) gl.glLightfv(gl.GL_LIGHT0, gl.GL_DIFFUSE, light_dif) angle = 0 win.set_visible() while not win.has_exit: win.dispatch_events() gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) gl.glClearColor(0.5, 0.5, 1, 1) gl.glLoadIdentity() gl.glRotatef(angle, 0, 1, 0) angle += 1.0 box.draw() win.flip() if __name__ == '__main__': _main()
with文って素敵ですね。