スコープ

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]
終わりに
  • Windowsでも高速なPython拡張が作れるようになったぞ!
  • MSVCコンパイラがある場合はSConsでSharedLibraryビルダを使ってビルドすると楽.
  • Pyrexのページではdistutilsを使う方法も紹介されていて,恐らくこちらの方が楽だと思うのだがWindowsだとビルドツール周りの設定が面倒だったので今回はパスした.

Exercise 1.6

解答

無限ループになる。
new-ifは普通の関数なのでnew-if評価時にthen式とelse式は両方とも評価される。
したがって毎回sqrt-iterの再起呼び出しが起きることになり、無限ループになる。

考察

sqrt-iterは末尾再帰になっている(と思うのだが)ので、スタックオーバーフローすらせずに本当に無限ループする?
TODO: 実装して確かめてみる。

第3回1000spekaersに参加して発表してきたことへの報告

今更ながら感想を書きます。

発表物についてはここにおいてあります。

良かった点

  • 空気を読まずにこういう発表会未体験者がいきなり申し込み、いきなり話したが思っていたより反応があったので良かった。
  • 2次会で直接お話しする機会が持てた吉岡さんはとても熱い人で楽しかった。
  • 自分より若くて積極的に活動している人達に会えて良い刺激になった。
  • サイボウズラボを見学出来た。学院時代の研究室を連想する楽しそうな雰囲気だった。
  • とりあえず「爆発しろ!」が流行っているのは理解した。

反省点

  • 直前に発表ネタを変更したこともありプレゼンに練りが足りなかった
  • 今までネット上での活動が少なく、知り合いや話のネタが少ないので話が持たない。
  • もうちょっと積極的に色々な人と話せれば良かった。相変わらず既成コミュニティに入っていくのが苦手な自分。
  • 都合によりサイボウズラボでの3次会を終電の時間でで途中退出してしまったのだがあの後も色々と面白かったようだ。
  • IRCの使い方をわかっていたつもりでわかっていなくて挙動不審だった。誰か優しく教えてください。
  • 感想を書くのが遅い俺爆発しろ!

Pygletで遊ぼう

cube

わずかな時間を見つけて 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文って素敵ですね。