V8 を使って Python で JavaScript を実行する

Google V8 JavaScript Engine の Python バインディングを試してみました。

直近で gumi Platform 上で簡単な OpenSocial ガジェットを動作させるのに使いたかったということが一番の動機ですが、サーバサイドで JavaScript を実行するというのは、これから当たり前の技術になると思っています。今後、モバイル・プラットフォームの台頭とクラウド・コンピューティングの進展により、非力なクライアントと強力なサーバという今までとは違ったパワーバランスになってきます。そこで重要になるのは、いかにクライアント側の処理をシンプルにするかということです。例えば、ブログパーツとかガジェットなんかは静的コンテンツを汎用的に表示させるためだけに JavaScript を使っています。最初からブログエンジン側でこの部分をレンダリングしてあげれば、非力なモバイル端末にとっては負担が減ります。このことは未だムーアの法則から取り残されているバッテリーの持ちにも影響してきます。

さて、早速試してみましょう。まずは、 bitbucket からソースコードをチェックアウトします。

$ hg clone http://www.bitbucket.org/dfdeshom/v8onpython/ v8onpython

次に libv8.so をビルドします。なお、ビルドには scons と Cython が必要ですので予めインストールしておきます。 scons は Ubuntu の場合はパッケージがあるので、 "aptitude install scons" (または apt-get)するだけです。 Cython も Python のパッケージ・システムを使って "easy_install Cython"するだけです。

$ cd v8onpython/v8-src
$ scons library=shared
...
scons: done building targets.

次に Python バインディングのビルドです。

$ cd..
$ python setup.py build_ext --inplace
running build_ext
building 'v8onpython' extension
g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-i686-2.5
/v8onpython.o -Lv8-src -lv8 -o v8onpython.so
$ mv v8-src/libv8.so .

正常に終了するとカレントディレクトリに v8onpython.so ができているはずです。それでは、実際に使ってみましょう。

$ ipython
Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52) 
Type "copyright", "credits" or "license" for more information.

In [1]: import v8onpython
In [2]: v = v8onpython.Script()
In [3]: v.compile("(function(){return 'Hello World'}())")
Out[3]: 'Hello World'

関連リンク:
» v8 - Google Code
» dfdeshom / v8onpython / source — bitbucket.org

Django Middleware で Traceback をコンソールに出力する

Django は、コンソールにデバッグ情報を出力してくれません(クリティカルなエラーは除く)。普通に開発している分には、ブラウザに表示される Traceback が充実しているので十分なのですが、API などブラウザ以外からリクエストが飛んでくるアプリケーションを開発している場合に、かなり不便なので Middleware を使ってコンソールに Traceback を出力するようにします。

まず、以下を middleware.py としてプロジェクト直下に保存。 request オブジェクトの内容も出力したい場合は、コメントを外します。

import traceback
import sys

class TracebackMiddleware():
    def process_exception(self, request, exception):
        print '######################## Exception ##################'
        print '\n'.join(traceback.format_exception(*sys.exc_info()))
        #print '-----------------------------------------------------'
        #print repr(request)
        print '#####################################################'

settings.py に ConsoleExceptionMiddleware の設定を追加。

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.middleware.doc.XViewMiddleware',
    'myapp.middleware.ConsoleExceptionMiddleware',
)

これでプログラム中でエラーが発生した際に manage.py を実行しているコンソールに Traceback 情報が表示されるようになります。以下サンプル。

関連リンク:
Django snippets: Middleware for printing of exception to console

Google I/O 第1日目

Google のディベロッパー向けカンファレンス「Google I/O」がついに始まりました。この日は,朝7:30に集合して,Google 渡部さんの助けを借りて Registration を済ませました。そして,たぶんこれはIOの間違いだと思うのですが,バイナリで「GOOGLEKO」と書かれたTシャツをもらって,会場に用意されている朝食を食べました。海外のエンジニアは,どんな人達なのか興味津々だったのですが,日本のエンジニアと雰囲気が似ていて親近感を覚えました。この日,世界各国から集まった技術者は3,000人だそうです。すごい…

Just before key note Keynote Speech

幕開けは,Vice PresidentのVic Gundtra氏によるキーノートスピーチでした。初日のすべてのセッションの中でこれが一番良かったと思ったくらい,本当に上手で非常に分かりやすいものでした。

内容としては,Googleが目指すところのおさらいだったのですが,scalability, distribution, storage,…といったキーワードのタグクラウドで表現していて,直近のテーマとして「Make the cloud more accessible」を中心に考えているそうです。また,ウェブ・アプリケーションがクライアント・アプリケーションに追いつく日は近いということも言っていました。
考えてみると,Googleという会社が今していることは,インターネットを徹底的に活用してインフラ的な地位を築いていくという点で一本の筋が通っていて全くブレがないように見えます。僕が知らないだけでこのような会社は他にもあるのかもしれませんが,少なくとも日本にはここまでの先進性を持った企業は確実にないような気がします。

Android

次に一番の目玉だと思われる Android 新バージョンのデモが行われました。iPhoneのような指でドラッグするようなインタフェースと,新機能のガジェット,そして電子コンパスと連動したストリートビューのアプリケーションが紹介されていました。Androidはバージョンが変わるごとに大きな進化を遂げていて,今年一番の注目株であることは間違いないようです。

Google App Engine

Google App Engine についていくつかの発表がありました。有料オプションが提供されるようになったことと Memcache と Image Manipulation の機能が使えるようになりました。そして,ついにディベロッパー登録の制限がなくなり誰でも利用可能になりました。 Memcache は汎用的な技術でちょっと意外な印象を受けたのですが,画像操作ができるようになったのは大きいですね。今まではサムネイルも作成できませんでしたから。

Google Web Tollkit

いまいち日本では盛り上がっていないように見える GWT ですが,Java 5 への対応と20%のスピードアップを果たしているそうです。Google社員の方に聞いたところによると,Google社内のJavaScriptエンジニアが手で書いたコードよりも速くて,そのエンジニアはショックを受けていたそう。また,BlueprintというGWTで書かれたビジネスアプリケーションのデモをしていたのですが,これが Flash と見間違えるほどの良い動きをしていました。また,Eclipse上で,ブレークポイントを設定してDOMの内容を確認するデモは興味深かったです。

OpenSocial

OpenSocial については,API v.0.8 が発表されたものの,内容についての詳しい説明はなくて,特に目新しい点はありませんでした。
OpenID, OAuth, OpenSocial の技術を軸に Web 上のあらゆる場所をソーシャルにしていくということ,現時点で2万人のディベロッパーがいるということを言っていました。あと,この Gundtra 氏は 「REST」を「リスト」と発音していましたが,他の人は「レスト」と言っていたので,レストでいいのだと思います。

Rapid Development with Python, Django and Google App Engine

そして, Python 言語の開発者で Google 社員の Guid van Rossam によるセッションを聞きました。Google App Engine で Django フレームワークを使うという内容で,正直そんなに面白くなかったのですが,生 Guido を拝めただけでも非常に感激でした。思わず,一緒に記念撮影をお願いしてしまいました。その時にGuidoが「日本人なのにRubyを使わないのか?」と言っていたのが,ちょっと面白かったです。

Guido at Python session  Guido

その後はパーティーに参加して,いろいろな人と名刺交換をしたりしたのですが,安藤恐竜さんの Android on Zaurus や佐々木さんのAndroidケータイが大人気で羨ましかったです。僕は英語が得意じゃないので,話をしていてもすぐにお互いに沈黙という状態になりがちなのですが,今度はそれを回避するためにも何かネタを用意していこうと思います。

その後,a2cさん,佐々木さんと Google App Engine の SDK1.1 を試したり,ホテル最上階のバーで軽く一杯飲んでから帰りました。

Google App Engine BoF  Blackberry and Night San Francisco

関連リンク:
Google I/O '08 Keynote: Client, Connectivity, and the Cloud

OpenCVを使った顔認識を試してみる

インテルが開発・公開しているオープンソースのコンピュータビジョンのライブラリ OpenCV を試してみました。コンピュータビジョン(computer vision)とは、「ロボットの目」を作る研究分野だそうです。画像処理や構造解析、物体追跡などたくさんの機能が実装されていますが、その中でも多くの人が試している顔認識をやってみました。


Photo by weboo

Fedora 8だとパッケージが用意されているので、下記のコマンドでインストールできます。

# yum install opencv opencv-python

そして、使ったプログラムがこれ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from opencv.cv import *
from opencv.highgui import *

def faceDetect(imgfile):
  # 画像を読み込む
  src_img = cvLoadImage(imgfile, CV_LOAD_IMAGE_COLOR)
  src_gray = cvCreateImage(cvSize(src_img.width, src_img.height),
                             IPL_DEPTH_8U, 1)

  # ブーストされた分類器のカスケードを読み込む
  cascade_name = "haarcascade_frontalface_default.xml"
  cascade = cvLoadHaarClassifierCascade(cascade_name, cvSize(1,1))
  
  # メモリを確保し,読み込んだ画像のグレースケール化,ヒストグラムの均一化を行う
  storage = cvCreateMemStorage(0)
  cvClearMemStorage(storage)
  cvCvtColor(src_img, src_gray, CV_BGR2GRAY)
  cvEqualizeHist(src_gray, src_gray)

  # 顔検出
  faces = cvHaarDetectObjects(src_gray, cascade, storage,
                              1.11, 4, 0, cvSize(40, 40))

  # 検出された全ての顔位置に枠を描画する
  for c, i in enumerate(faces):
    pt1 = cvPoint(int(i.x), int(i.y))
    pt2 = cvPoint(int(i.x + i.width), int(i.y + i.height))
    cvRectangle(src_img, pt1, pt2, CV_RGB(255,0,0), 3, 8, 0);

  return src_img

if __name__ == '__main__':
  if len(sys.argv) < 2:
    print "Usage: facedetect <filename>\n" ;
    sys.exit(-1)
  img = faceDetect(sys.argv[1])

  # 画像を出力
  cvSaveImage("output.jpg", img)

これはいろいろ応用ができそうで面白いですね。haarcascade_frontalface_default.xml のパターンデータが肝なんだけど、自分で学習させようとする場合は、背景画像が3,000通り、対象物の画像が7,000通りくらい必要だそう。ただし、トレーニング用の画像を自動生成することも可能なようなので頑張ればできなくもなさそうですね。

その後、erogeekな人のプロジェクトはどうなったんだろう…

OpenCV プログラミングブック
奈良先端科学技術大学院大学
毎日コミュニケーションズ (2007/09/22)
売り上げランキング: 4367

 

関連リンク:
Open Source Computer Vision Library
opencv.jp - OpenCV サンプルコード
OpenCV for Linux

Mecabでキーワード自動リンク(Python編)

ウノウラボ Unoh Labs: PHPとMecabでキーワード自動リンクを実装する

上記の記事ではPHPでの使い方を紹介しましたが、もちろんPythonでも簡単にできます。1.mecabインストールと2.自動リンク用辞書の作成は、同じなのでここでは省略します。

3.Mecabモジュールのインストール

# yum install python-mecab

はい、完了。

4.動作確認

次のようなプログラムを書いて実行します。

import MeCab

t = MeCab.Tagger("-d /usr/lib/mecab/autolink")
str = '京都についてGoogleとYahooで検索した。'

print t.parse(str)

実行結果

$ python autolink.py
<a href="http://www.city.kyoto.jp/">京都</a>について
<a href="http://www.google.com/">Google</a>と
<a href="http://www.yahoo.com/">Yahoo</a>で検索した。

このようにPHPと同じかそれ以上にとても簡単です。
Pythonの場合は、他にTernary Search Tree(TST)のデータ構造を利用したpytstもあるのですが、日本語のことを考えるとMecabかDartsのほうが安心かと。

また、さらなる高速化を追い求める人は、 cmecab -- Mecab-Python高速バインディング というのもあるので、こちらも試してみると楽しいかもしれません。

MeCabとPythonでマルコフ連鎖を書いてみる(改)

Python界でも自分で書いたプログラムを晒すとえらい人が添削してくれます、という仕組みはまだないですが、ちょうど時を同じくしてマルコフ連鎖のプログラムを書いている人がいました。

マルコフ連鎖プログラム - pyletの日記


なるほど、マルコフ辞書のキーはタプルにしたほうがスッキリしますね。こういうサンプルソースを公開してくれる人がいると勉強になるなぁ。ありがとうございます。> pyletさん
というわけで、 こないだのプログラム に手を入れてみました。だいぶスッキリしたみたい。これなら、連鎖数3にも5秒でできるよ。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import random
import MeCab

def wakati(text):
    t = MeCab.Tagger("-Owakati")
    m = t.parse(text)
    result = m.rstrip(" \n").split(" ")
    return result

if __name__ == "__main__":
    filename = "test.txt"
    src = open(filename, "r").read()
    wordlist = wakati(src)

    # Create table of Markov Chain
    markov = {}
    w1 = ""
    w2 = ""
    for word in wordlist:
        if w1 and w2:
            if (w1, w2) not in markov:
                markov[(w1, w2)] = []
            markov[(w1, w2)].append(word)
        w1, w2 = w2, word

    # Generate Sentence
    count = 0
    sentence = ""
    w1, w2  = random.choice(markov.keys())
    while count < len(wordlist):
        tmp = random.choice(markov[(w1, w2)])
        sentence += tmp
        w1, w2 = w2, tmp
        count += 1

    print sentence

『プログラムは、人々がそれを読むために書かれるべきである。
たまたま、それが計算機で実行されるにすぎない。』 - Gerald Sussman

MeCabとPythonでマルコフ連鎖を書いてみる(連鎖数2)

PythonでMeCabを使ったわかち書きができるようになったので、マルコフ連鎖のプログラムを書いてみました。
MeCabとPythonで遊んでみたメモ(パパパパパイソン) に載っているものを参考に連鎖数を2に変更してみました。

パパパパパイソンさんのと同じ文章を元に自動生成した文章がこれです。やっぱり連鎖数1の時よりも、それらしい文章を出力してくれるようです。まぁ、意味が分からないのは一緒ですが。

それは書生という人間中で一番獰悪な種族であったそうだ。この書生というのは時々我々を捕えて煮て食うという話である。掌の上で少し落ちついて書生の顔を見た。はここで始めて人間というものである。名前はまだ無い。どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙を吹く。どうも咽せぽくて実に弱った。これが人間の飲む煙草というものである事はようやくこの頃知った。これが人間の飲む煙草というものである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう。この書生という人間中で一番獰悪な種族であったそうだ。その後猫にもだいぶ逢ったがこんな片輪には一度も出会わした所でニャーニャー泣いていた事だけは記憶している。第一毛をもって装飾されべきはずの顔がつるつるしている。第一毛をもって装飾されべきはずの顔を見たのがいわゆる人間というものの見始であろう。

次に某芸能人のブログの文章を元にしたものがこれ。もともと意味分かんないから、本人が書いたと言われても分からない気がする…

ルナ溺愛する!前世から のデスティニー!チャットおもしれえ(゜∀゜)(゜∀゜)(゜∀゜)━━━━━━カワユス!キティマ ミタス!キティマミタスDSギザカワユス地球大図鑑いただいてしまった!うれしい(´;ω;`)なんかGJってコメントが沢山ですごいお(^ω^)セラムンコス貪欲にとりくんでプロクオリティめざしてるとこだお

ソース

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import random
import MeCab

def wakati(text):
    t = MeCab.Tagger("-Owakati")
    m = t.parse(text)
    result = m.rstrip(" \n").split(" ")
    return result

if __name__ == "__main__":
    filename = "test.txt"
    src = open(filename, "r").read()
    wordlist = wakati(src)

    # Create table of Markov Chain
    markov = {}
    prev1 = ""
    prev2 = ""
    for word in wordlist:
        if prev1 and prev2:
            if not markov.has_key(prev2):
                markov[prev2] = {}
            if not markov[prev2].has_key(prev1):
                markov[prev2][prev1] = []
            markov[prev2][prev1].append(word)
        prev2 = prev1
        prev1 = word

    # Generate Sentence
    count = 0
    prev2= random.choice(markov.keys())
    prev1 = random.choice(markov[prev2].keys())
    sentence = prev2 + prev1
    while count < 100:
        tmp = random.choice(markov[prev2][prev1])
        sentence += tmp
        prev2 = prev1
        prev1 = tmp
        count += 1

    print sentence

関連リンク:

改良版です → MeCabとPythonでマルコフ連鎖を書いてみる(改)

MecabのPythonバインディングを使ってみる

例によって、FedoraとCentOS用のPRMパッケージを作りました。利用するには、先に Mecabをインストール しておく必要があります。


インストールしたら、さっそく試してみましょう。次のようなサンプルプログラムを書きます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import MeCab

sentence = "太郎はこの本を二郎を見た女性に渡した。"

try:
    t = MeCab.Tagger()
    m = t.parseToNode(sentence)
    while m:
        if m.stat < 2:
            print m.surface, "\t", m.wcost, "\t", m.feature
        m = m.next
except RuntimeError, e:
    print "RuntimeError:", e;

実行すると、下記のような結果が出力されます。

太郎    7473    名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は      2919    助詞,係助詞,*,*,*,*,は,ハ,ワ
この    1017    連体詞,*,*,*,*,*,この,コノ,コノ
本      5040    名詞,一般,*,*,*,*,本,ホン,ホン
を      2670    助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
二郎    5667    名詞,固有名詞,一般,*,*,*,二郎,ニロウ,ニロー
を      2670    助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
見      6208    動詞,自立,*,*,一段,連用形,見る,ミ,ミ
た      3939    助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
女性    2278    名詞,一般,*,*,*,*,女性,ジョセイ,ジョセイ
に      3622    助詞,格助詞,一般,*,*,*,に,ニ,ニ
渡し    5101    動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た      3939    助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      -12     記号,句点,*,*,*,*,。,。,。
ふむふむ、この結果から名詞だけを取り出して、単語生起コストをごにょごにょすればよいわけですね。
"二郎"が"ニロー"になってるけど、固有名詞だからしょうがないのかな…

Pythonで書いたスクリプトをデーモン化する方法

某プログラムをcronで呼び出していたのですが、これだと1分おきにしか実行できません。次のようにすれば、30秒単位で実行することもできますが、通常は1分単位で、ある時は10秒単位でというように状態によって間隔を変えて処理したかったのでデーモン化の方法を調べました。

* * * * * date & sleep 30 ; date

Chris' Python Page にてそのものずばりの例を発見しました。指定したユーザで実行できるし、init.dスクリプトを書けば、OS起動時に自動実行することもできます。すばらしー

10秒おきにログ(/var/log/pydaemon.log)に時間を書き出すサンプル
#!/usr/bin/env python

###########################################################################
# configure these paths:
LOGFILE = '/var/log/pydaemon.log'
PIDFILE = '/var/run/pydaemon.pid'

# and let USERPROG be the main function of your project
#import mymain
#USERPROG = mymain.main
###########################################################################

#based on Jurgen Hermanns http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
import sys, os

class Log:
    """file like for writes with auto flush after each write
    to ensure that everything is logged, even during an
    unexpected exit."""
    def __init__(self, f):
        self.f = f
    def write(self, s):
        self.f.write(s)
        self.f.flush()

def main():
    #change to data directory if needed
    os.chdir("/tmp")
    #redirect outputs to a logfile
    sys.stdout = sys.stderr = Log(open(LOGFILE, 'a+'))
    #ensure the that the daemon runs a normal user
    os.setegid(500)     #set group first "pydaemon"
    os.seteuid(500)     #set user "pydaemon"

    #start the user program here:
    import time
    while 1:
        print time.ctime(time.time())
        time.sleep(10)

if __name__ == "__main__":
    # do the UNIX double-fork magic, see Stevens' "Advanced
    # Programming in the UNIX Environment" for details (ISBN 0201563177)
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

    # decouple from parent environment
    os.chdir("/")   #don't prevent unmounting....
    os.setsid()
    os.umask(0)

    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent, print eventual PID before
            #print "Daemon PID %d" % pid
            open(PIDFILE,'w').write("%d"%pid)
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

    # start the daemon main loop
    main()
通常は、実際に実行するプログラムを別に用意しておいて、呼び出すのでしょうね(import mymainの箇所を参照)。