2011年1月26日水曜日

Custom XmlAdapter for Joda Time with xjc

JAXBを使ってますか?
JAXBとJoda-Timeを連携してみましょう。

以下のクラスとXMLをBindするのが目標です。
public class Foo {
  private DateTime bar;
  private DateTime baz;
}
<foo>
  <bar>2011-2-3</bar>
  <baz>2010-3-4T05:06:07</baz>
</foo>

JAXBでは面倒な点がいくつかありました。
1. @XmlType(propOrder)にフィールドを列挙する必要がある。
2. xs:dateはXmlGregorianCalendarに割り当てられている。

うーん、面倒ですね。GroovyだったらとかClojureだったらとか、思わずにはいられない。
それぞれの対策を書いていきます。

1. @XmlType(propOrder)にフィールド名を列挙する必要がある。
アノテーションでは順序が保たれないため、フィールドとpropOrderの双方に書く必要がある。
とりあえず生成するGroovyコードを書いたのですが、調べたらxjcなんてものがあったので、移植してみました。
かんたんなスキーマを書いてみます。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="foo">
    <xs:complextype>
      <xs:sequence>
        <xs:element name="bar" type="xs:date" />
        <xs:element name="baz" type="xs:dateTime" />
      </xs:sequence>
    </xs:complextype>
  </xs:element>
</xs:schema>
生成されたbar,bazの型は、見慣れないXmlGregorianCalendar なんだこれ。

2. xs:dateはXmlGregorianCalendarに割り当てられている。
http://weblogs.java.net/blog/kohsuke/archive/2006/03/how_do_i_map_xs.html
xjcの開発者であるkohsukeのブログでXmlGregorianCalendarを使わない方法があったので参考にしました。
<xs:schema elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0" targetNamespace="calendar-schemalet">
  <xs:annotation><xs:appinfo>
    <jaxb:globalBindings>
      <jaxb:javaType name="java.util.Calendar" xmlType="xs:date"
        parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
        printMethod="javax.xml.bind.DatatypeConverter.printDate"
        />
    </jaxb:globalBindings>
  </xs:appinfo></xs:annotation>
</xs:schema>

なるほど。変換するメソッドを呼び出すことができるわけですね。
この例では準備されたメソッドですが、任意のメソッドを呼び出すことができます。
標準のはCalendarなので、java.util.Dateに変換するのを書けばいいだけ。
簡単ですね。
これでどんな型でも自由自在。
xs:appinfoを別のファイルに移し、xs:includeすると共通化できて便利です。
うまく名前空間を利用するとインクルードなしでもいけるんでしょうが。

ここで一つ別の問題が出てきました。

3. 余計なAdapterが生成される
Adapter1やAdapter2が勝手に生成されてしまいます。
できればこれらの名前も指定したいところ。
最終的にはこうなりました。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.0">
  <xs:annotation>
    <xs:documentation>custom datetime adapter</xs:documentation>
    <xs:appinfo>
      <jaxb:globalBindings>
        <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateAdapter" xmlType="xs:date"/>
        <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateTimeAdapter" xmlType="xs:dateTime"/>
      </jaxb:globalBindings>
    </xs:appinfo>
  </xs:annotation>
  <xs:element name="foo">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="bar" type="xs:date" />
        <xs:element name="baz" type="xs:dateTime" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
Adapterはこんな感じ。
日付の形式も自由自在。ただしおすすめはできないけど。
読みたいこともあるよね。
public class DateTimeAdapter extends XmlAdapter<String, Datetime> {
 private static final DateTimeFormatter YMDHMS = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss");
 @Override
 public DateTime unmarshal(String v) throws Exception {
  return YMDHMS.parseDateTime(v);
 }

 @Override
 public String marshal(DateTime v) throws Exception {
  return YMDHMS.print(v);
 }
}

以下はテストコードと、xjcの実行。
Antタスクもあったんだけど、最近build.xmlを書いてないから構文を忘れた。
Gradleかわいいよ

@Test
  public void readwrite() throws Exception {
    StringWriter sw = new StringWriter();
    Foo foo = new Foo();
    foo.setBar(new DateTime(2011, 2, 3, 4, 5, 6, 7));
    foo.setBaz(new DateTime(2012, 3, 4, 5, 6, 7, 8));
    JAXB.marshal(foo, sw);
    StringReader sr = new StringReader(sw.toString());
    Foo foo2 = JAXB.unmarshal(sr, Foo.class);
    assertThat(foo2.getBar(), is(new DateTime(2011, 2, 3, 0, 0, 0, 0)));
    assertThat(foo2.getBaz(), is(new DateTime(2012, 3, 4, 5, 6, 7, 0)));
  }

  public static void main(String[] args) throws Exception {
    com.sun.tools.xjc.Driver
        .main("-no-header -extension -d src/test/java -p hikoz.xml src/test/java/hikoz/xml/foo.xsd"
            .split("\\s+"));
  }

2010年12月22日水曜日

Werkzeugのデバッガ

鹿に尻をかじられたりもしましたが、
三日間の奈良旅行で、8万歩以上も歩き、とても充実した休暇をすごしました。

Python Web フレームワーク アドベントカレンダー2010 のバトンがまわってきたので、Werkzeugの紹介をします。
http://werkzeug.pocoo.org/

Werkzeugはみなさんご存知のFlaskの作者であるArminが作ったWEBフレームワークです。
実はFlaskのベースでもあります。
単体で使っても便利ですが、非常によい機能が詰め込まれているため、各種フレームワークに組み込んで使う人が多いようです。
たとえば、django-extensionsでDjangoに組み込んで使う人も多いでしょう。
私も、どんなWEBアプリケーションでもほとんどの場合、開発時に組み込んでいます。
運用時にも使うケースは少なくありません。

丁寧なチュートリアルもついているのですが、なにぶん、生のWSGIが露出しているフレームワークなので、
分かりやすいとは言えません。
今回は、便利な機能の一部に焦点を合わせて解説します。

さて、解説前に少し準備しましょう。
まず、ごく小さなアプリを一つ書きます。
hello.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from werkzeug import Response, script

def app(environ, start_response):
    response = Response('Hello, World!')
    return response(environ, start_response)

action_runserver = script.make_runserver(lambda: app)
action_shell = script.make_shell(lambda: {})

if __name__ == '__main__':
    script.run()

pythonでライブラリを試すときはVirtualenvが便利です。
wget https://bitbucket.org/ianb/virtualenv/raw/eb94c9ebe0ba/virtualenv.py
python virtualenv.py ~/.virtualenvs/pyadvc
source ~/.virtualenvs/pyadvc/bin/activate
pip install werkzeug
python hello.py runserver

http://localhost:5000/にアクセスするとHello, World!と表示されるはずです。

デバッガ

JavaやC#ではステップ実行を使ってデバッグするケースがあると思います。
Pythonでもpdbを使ってステップ実行ができますが、ブレークポイントを設定するのが面倒ですね。
WerkzeugではアプリケーションがExceptionをWerkzeugに投げたのを捕まえて、WEB画面上でステップ実行をすることができます。
ためしに一つエラーを起こしてみましょう

def app(environ, start_response):
    a = 'Hello'
    response = Response('%a, World!'%a)
    return response(environ, start_response)
app関数を書き換えてもう一度起動してみましょう
するとおなじみのInternal Server Errorが出るはずです。
これではエラーの原因がわかりません。
ただ、コンソールでは
response = Response('%a, World!'%a)
 ValueError: unsupported format character 'a' (0x61) at index 1
というように表示されているので、エラーの推測はできるでしょう。
ここで、%aが正しくないことはわかったのですが、なにが正しいのでしょうか。
そこでデバッガです。

python hello.py runserver --debugger
という引数をつけて起動しましょう。
再度表示してみると、カラフルな画面が表示されるようになりました。

Traceback (most recent call last)

  • File "/home/spam/ws/python/wz/hello.py", line 7, in app

    response = Response('%a, World!'%a)

response = Response('%a, World!'%a)

と書かれた行をクリックしてみましょう。
これでソースが確認できます。

さらに、その行の右にコンソールっぽいアイコンがありますのでクリックします。
すると[console ready]と表示されました。
これはPythonのコンソールです。
たとえば 1 + 1とすると2と返答があるはずです。
さて、直したいのは
'%a, World!'%a
の部分です。
まず、dump()とすると、変数一覧が表示されます。

>>> dump()

Local variables in frame

a'Hello'
start_response
environ{'wsgi.multiprocess'False'SERVER_SOFTWARE''Werkzeug/0.6.2''SCRIPT_NAME''''REQUEST_METHOD''GET'  }

aに'Hello'が入っているのがわかりますね。

ここで、 '%a, World!'%a と入力すると先ほどと同じエラーが出ます。
a,b,cといろいろ試したり、マニュアルを確認したところ、%sが正しいとわかりました。
>>> '%s, World!'%a 'Hello, World!'


これで、正しい文がわかったので、ソースを書き換えて終了です。


その他、Context LocalsというJavaでいうThreadLocalを便利にした機能から、WEBアプリに必須なルーティング機能まで、すばらしい品揃えです。
そのWerkzeugの機能を丁寧にラップして、高速+多機能テンプレートエンジンであるJinja2と組み合わせたのがFlaskです。

ぜひ試してみてください。

次は@lirisさんお願いします。

2010年8月19日木曜日

Why Corel MovieWriter sucks

プライベートのDVDを作成していたんだけど、CorelのDVD作成ソフトであるMovieWriterがひどすぎて、怒りが収まらない。
たった一枚のDVD-Videoのために、友人たちが取ってくれた合計40本程度の動画を細かく繋いだところ、
重すぎて映像を選択するだけで3分待たされるのはまだ許す。
映像の再生もしてない、ただタイトルをマウスでクリックするたびになんの計算をしてるんだと考えると腹立たしいけど。

イライラを抑えながらなんとか最後のDVD出力までたどり着く。
出力開始40分後、今度は突然、異常終了する。
なんのエラーメッセージも吐かないから対応も取りづらい。
MP4からHDまで多様な形式をつないでいるので、相性の悪いフォーマットがあるのかなと、
とりあえずチャプターを減らして、正常に終了したところとかかった時間から、不具合のあるチャプターを特定する。
しかし、不具合のあるチャプターも、本来の3分の1程度の長さでDVDをつくれば正常に出力されてしまうので
特定が難しいのに加えて、テストに時間がかかる。

つないだ映像の本数が多いし、形式も多様だから難しいのだろうと気を使わせる時点で
すでに値段の価値もないソフトウェアですね。
最初から対応してないって分かっていれば全部一度ffmpegでmpegに統一するよ。
しょうがないから作れる範囲の短いDVDで出力し、あとから自分でインポートしなおせば、
形式も統一だし、ソース本数が減るから焼けるようになるはず。

しかし、ここでも問題が発生。
チャプターの数も30に満たない数しかないのに、チャプター数の限界に達したとして
DVD書き込み中に中断。

これも何度か繰り返して特定。
原因は、映像に暗転をいれていたところ、0秒のチャプターが存在していたため。
エラーメッセージ間違ってるよ。
そもそも映像変換中に気づいてくれよ。
DVD書き込み中に中断したらDVDが一枚無駄になるんですけど。

それまでは警戒して、ファイル出力にしていたけど、今回は映像の編集も変換もないので
問題がないだろうと、つい油断してDVD出力に設定し寝てしまっていた。
信じちゃいけなかった。

そして、数々の試練を乗り越えて、ついに一枚出力完了。
ここまで2週間もかかった。
しかし、すでに疑心暗鬼の私は安心をしない。
もちろん再生して確認する。
どこでどんなトラブルがあっても驚かないぞと思っていたけどまさかね。
なんとまあ、あきれてしまう。
自分で作ったものをインポートしたら音声が全て消えるのはひどすぎるないか?

一回出力するのに40分程度はかかるから、一晩なんてあっという間。
あんな重いソフトウェアでも一応映像の切り貼りだけは三日で終わったのにそこからが長すぎる。
結果として、5GのDVD一枚をまともにつくることができずに2週間経過。
これでBlu-ray対応なんて笑わせる。

ここまでの数々の問題でも使えないことが明白ですが、もっとひどい問題もある。
MTSという形式があります。
HD動画の形式だけど、その動画も読み込んで編集することができるのはすばらしい。
出力も成功するのですが、実際は内部のデコードで失敗していて、映像が乱れてそれ以降の映像はとび、音声がずれるという悲惨な状態。
これを成功として、再生するまでわからないようにするのは良識を疑う。

金返せじゃすまない。
2週間の睡眠時間とその間の体調不良をどうしてくれる。

2010年1月22日金曜日

Play framework is a full stack framework

Play frameworkで楽しんでます。
フルスタックとはテンプレート、データベース補助があるだけでは不足

clean url routing, test, fixture, message after redirect, background job, cache, i18n程度あたりがそろっていないとFullとは言えない時代だと思う。
Playはその点、完璧だろう。
reverseまで持ってるんだから、私は当然お気に入り。

ただ、欠点もある。

background jobがスレッドで走ってしまうので、重くなる。
Hibernateとは別れたい。
ほかには、先日書いた内容など。

やはり私はpublic fieldを積極的に使いたい。
メソッドではなくフィールド
redirect(BookPage.find, 1)とかでリダイレクトしたいし、
tx(BookService.reserve, 2)とかでトランザクションを開始したい。

Routingもフィールドを使えばリファクタリング効くしね。

メソッドをフィールド扱いできるScalaには期待しています。

2010年1月21日木曜日

ANA-Amex

JALが沈没したのでクレジットカードを整理することにしました。

次はANA-AMEXにします。
アメリカン・エキスプレス

12月までは大盤振る舞いだったようだけど、まだ大丈夫か

1. 100円で2マイル
3ヶ月間ポイントが倍

2. 入会2000+1000マイル

3. オンライン明細で1000マイル
ただし、入会時は紙にしておいて、入会したあとに変更すること

4. 公共料金や電子マネー、携帯などの引き落としでそれぞれボーナス
合計すると10000マイルの予定

5. Edyチャージでポイントがつく
ただし6ヶ月間

6. 年会費がAMEXにしては安い
5250円

スルガ銀行ANA支店

2010年1月15日金曜日

Hello, Play framework

Play frameworkを使ったことがある人はどれぐらいいるんだろうか。
こんなに面白いフレームワークとは思わなかった。
いや、フレームワークというより、プラットフォームです。

まえにJava on Railsなんて宣伝を見てしまったせいでスルーしていた。
たしかに「Rails使いに媚びてDjangoをJavaにポートした」と言えないこともないけど。
ためしてから宣伝してください。迷惑です。
見所はコマンドラインなんかじゃない。まあ、Python for Windowsがついてくるのは確かに面白いけど。
なんといってもJavaの使い方が革命的に面白い。

なんと表現すればいいのか。
「コンパイルさえ通せば、あとはまかせとけ」
といったところか。
staticメソッドを積極的に使い、記述をシンプルにすることが使命であるようだ。

まずはこの三つの機能を見てみます。
1. Bind an HTTP parameter to a Java method parameter
リクエスト引数をメソッド引数に名前で結びつけちゃいます。
メソッド引数名ですよ?
アノテーションなんて不要です。
まさしく名前つき引数なのです。

routesに
GET  /show/{name}  Application.show
とあったとして、
/show/Tom?page=2
にアクセスがあったならば
public static void show(String name, Integer page)
では、nameにTomが入り、pageは2になるのです。
型や順番や偶然を使っているわけではありません。
引数名を見ているのです。

2. Redirect to an action by calling the corresponding Java method
一度でもDjangoのreverseを使ったことがあれば、URLなんて直接書きたくないと思うことでしょう。
この場合ならばControllerのメソッド名で指定したいところ。
しかし、文字列でredirect(reverse("Books.list", page))では芸がない。
redirect(Books.list, page)と書くことができればいいけど、残念ながらコンパイルエラーになるでしょう。
Javaのメソッドとフィールドの名前空間と同じであれば、もっとまともなフレームワークを作れたのですけどね。
そこでPlayは突き抜けました。
Books.list(page)
と書けば勝手にリダイレクトされます。

staticメソッドだからといって気を抜いてはいけません。
フォワードではなく、間違いなくブラウザにResponseがかえります。

3. Don’t Repeat Yourself when passing Java objects to templates
Spring MVCではデフォルトの名前というものが存在します。
User => user, List<User> => userList
というように、型から名前をつけてくれるので、テンプレートに名前をつけながら渡す必要がありません。
しかし、Playでは1と同様に型ではなくローカル変数名を取得します。
ローカル変数名ですよ。
pythonでもrender(article=article, user=user)と書かなきゃいけないってのに。
bytecode enhanceってレベルじゃないですね。
ただ、残念ながら実行時に解決なので、いっそコンパイラを作って変数名をbytecodeに埋め込んでほしいと思います。


以上のようにJavassistの黒魔術を駆使して理想郷を追い求めます。

だがちょっと待ってほしい。
この黒魔術
まさにDIでやっていることそのままではないか。
DIの黒魔術を許してこれを許さないなんて、なんという傲慢。
フィールドに準備しておくか、staticで呼び出すかの違いしかない。
ルールを決めておけばOKとはどの口が言ったものか。
Mockitoを見て便利だといっていたじゃないか。
staticであればコードがそのまま追えるなんて、いつまで20世紀にしがみついているんだ。
もうそんな時代じゃないんだ。
EJB2信者なんて、もういないでしょ?

DIに飼いならされた開発者たちなら、ルールだと説得すればすぐ納得するね。
賭けてもいい。
逆に、「Proxyを使って実現できるDIとは違う」と反論するような人が相手なら、
メリットを比較してあげれば、いずれは「ひでえ」といって納得するはず。

残念なところもいくつか挙げておく。

1. アプリの構造が貧弱
Djangoのアプリを引き継いでほしかった。
一定以上の規模のものを作れるのだろうか。
心配です。
2. formが貧弱
ModelとはべつにFormクラスを作ってください。
ModelべったりのFormだと画面や権限に応じてValidationや項目を変えづらい。
3. Bytecode拡張される範囲が不明
Bytecode拡張される範囲が明確でないため、自分で拡張しようとすると
なかなか恐ろしい。
たとえば、もう少し複雑で動的なFormがあって、複数のControllerで共有したいとすると、
自分で独自のクラスを作りたくなるけど、どこまで拡張してくれるのか試すまで、
いや、試してもよくわからない。
returnをなくそうとしたり、Javaの言語仕様をとことん無視しようとしているので、
なかなか機能拡張は難しそう。

まとめ

二週間以内で作るものにはちょうどいいかも。
どうしてもJavaがいい。
Javaじゃなきゃいやと根拠なく言われたときに使うことを考えてもいいかもしれない。
Springの起動が遅くて、決別したいとは常々思っているので、
こういうアプローチが流行ってくれば面白いとは思っています。
Bytecode拡張以外は使いやすく、わかりやすい機能がてんこ盛りなのでお勧めです。
Bespinとうまく連携すれば、全文検索つきのCMSが結構簡単に作れるかも。

2009年12月16日水曜日

aws with python

botoがよろしい
boto
結構パッチを取り込んでもらったのでだいぶ使いやすくなった。

botoドキュメント

amiをEBSへ変換する
Amazon EC2 – Boot from EBS and AMI conversion

botoで変換するとこんな感じ
Creating an EBS-backed AMI from an S3-backed AMI

いろいろ自動化できて楽しい。
AMI内でサーバを再起動するより、裏で立ち上げてElastic IPをすりかえてしまうとか
いろいろ試せて楽しい
Spot Instanceにも、とりあえずだけど対応してもらった

felica with python

felicalibが主流。
felicalib
ただしWindowsのみ

Pythonからの使用例(ctypes)
FeliCa の Id を python で読む(その2)
Python: PaSoRiでSuicaの履歴を読み出す

Javaからの使用例(JNA)
'PaSoRi'を使用して、FeliCaカード内の'IDm'を読み取る
Javaの応用:Felicaカードリーダ

libpafe(Linux用)
libpafe

2ch
■◇FeliCa でソフトを作りまくるスレ◇■
FeliCa でソフトを作りまくるスレ 2ブロック目

追記 12/24 18:00
三者間通信
三者間通信 - 新・招き猫のページ
PaSoRi「パソリ」(RC-S320/RC-S330)三者間通信 サンプル

SDK For Felica + AIR
SDK for FeliCa & Adobe AIR
FlashDevelop.jp
フリーのFlash統合開発環境 FlashDevelop (+flex 3 SDK)を入れてみました

2009年9月30日水曜日

String concatenation in Python

文字列連結の効率の話
addのほうが早かったよなと思って、色々試してみた。

def str_mul():
    return name * count

def str_join():
    lst = []
    append = lst.append
    for i in xrange(count):
        append(name)
    return ''.join(lst)

def str_map():
    lst = map(lambda x:name, xrange(count))
    return ''.join(lst)

def list_comp():
    return ''.join(name for i in xrange(count))

def str_add():
    s = ''
    for i in xrange(count):
        s += name
    return s

import cStringIO
def cstring_io():
    io = cStringIO.StringIO()
    write = io.write
    for i in xrange(count):
        write(name)
    return io.getvalue()

import StringIO
def string_io():
    io = StringIO.StringIO()
    write = io.write
    for i in xrange(count):
        write(name)
    return io.getvalue()

from array import array
def str_array():
    a = array('c')
    add = a.fromstring
    for i in xrange(count):
        add(name)
    return a.tostring()

from mmap import mmap
def str_mmap():
    m = mmap(-1, count * len(name))
    write = m.write
    for i in xrange(count):
        write(name)
    m.seek(0)
    return m.read(count * len(name))

def main():
    global count, name
    func_list = (str_mul, str_join, str_add, string_io, cstring_io,
                 str_map, list_comp, str_array, str_mmap)

    # test
    count = 2
    name = 'hello'
    assert all((name * count).__eq__(f()) for f in func_list)

    import timeit
    for c in (10, 1000):
        for b in (1, 1000):
            for f in func_list:
                count = c
                name = 'hello' * b
                print '%10s x %4s, "hello"*%4s: %9.4fms'%(
                    f.func_name, c, b, timeit.timeit(f, number=100)/100*1000)

if __name__ == '__main__':
    main()


   str_mul x   10, "hello"*   1:    0.0006ms
  str_join x   10, "hello"*   1:    0.0042ms
   str_add x   10, "hello"*   1:    0.0033ms
 string_io x   10, "hello"*   1:    0.0312ms
cstring_io x   10, "hello"*   1:    0.0072ms
   str_map x   10, "hello"*   1:    0.0059ms
 list_comp x   10, "hello"*   1:    0.0077ms
 str_array x   10, "hello"*   1:    0.0081ms
  str_mmap x   10, "hello"*   1:    0.0180ms
   str_mul x   10, "hello"*1000:    0.0108ms
  str_join x   10, "hello"*1000:    0.0131ms
   str_add x   10, "hello"*1000:    0.0126ms
 string_io x   10, "hello"*1000:    0.0411ms
cstring_io x   10, "hello"*1000:    0.0364ms
   str_map x   10, "hello"*1000:    0.0146ms
 list_comp x   10, "hello"*1000:    0.0167ms
 str_array x   10, "hello"*1000:    0.0377ms
  str_mmap x   10, "hello"*1000:    0.1105ms
   str_mul x 1000, "hello"*   1:    0.0017ms
  str_join x 1000, "hello"*   1:    0.2352ms
   str_add x 1000, "hello"*   1:    0.2235ms
 string_io x 1000, "hello"*   1:    2.4272ms
cstring_io x 1000, "hello"*   1:    0.5111ms
   str_map x 1000, "hello"*   1:    0.3591ms
 list_comp x 1000, "hello"*   1:    0.2296ms
 str_array x 1000, "hello"*   1:    0.5338ms
  str_mmap x 1000, "hello"*   1:    0.4778ms
   str_mul x 1000, "hello"*1000:    8.5971ms
  str_join x 1000, "hello"*1000:    8.3908ms
   str_add x 1000, "hello"*1000:   29.4590ms
 string_io x 1000, "hello"*1000:   10.6351ms
cstring_io x 1000, "hello"*1000:   28.7235ms
   str_map x 1000, "hello"*1000:    8.8407ms
 list_comp x 1000, "hello"*1000:    8.2300ms
 str_array x 1000, "hello"*1000:   38.9228ms
  str_mmap x 1000, "hello"*1000:   18.6053ms

addは50kB程度までは、文字列長や回数にかかわらず早い。
'str' * nをjoinが逆転するのは見てびっくり。
StringIOは1MBを超えたあたりで突然加速、理由不明。
cStringIOを余裕で追い抜き、100MBあたりではjoinに並ぶ。
ただし、1MBを超えるようなものはPythonで扱うべきではないかも。
for, map, list内包は勝ったり負けたり。
disってみたところ、早そうなのはfor-join。
psycoも使ってみたけど、速度はあまりかわらず。

結論:
addでもjoinでも扱いやすいほうを使おう。
速度はPythonを使う上では気にしない。

2009年9月14日月曜日

Interpolation surprise — And now for something completely Pythonic...

Interpolation surprise — And now for something completely Pythonic...

おそろしや

class Surprise(object):
def __str__(self):
return "[str]"
def __unicode__(self):
return u"[unicode]"

surprise = Surprise()

print "%s %s %s" % (surprise, u"foo", surprise)

出力:
[str] foo [unicode]

つまり、途中でUnicodeが来ると、それ以降は__unicode__が呼ばれるようになるわけです。


ちなみにPython2.3なら
出力:
[str] foo [str]
となります。

2009年9月3日木曜日

setuptools

SolacePlurkからリリースされました。

Stack Overflowのクローンです。
あいかわらず仕事が早いしソースが読みやすい。

しかし今回、最も驚いたのが、setup.pyの使い方。
こんな便利なものだったんですね。
なお、正しい手順はREADMEInstallationを見てください。

私の手順は以下のとおり。

$ hg clone http://bitbucket.org/plurk/solace
$ mkvirtualenv solace
(solace)$ cd solace
(solace)$ python setup.py develop
(solace)$ echo SECRET_KEY = \'`mkpasswd -l 40`\' > config.py
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py reset
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py runserver

はやい!本当に早い!
setup.pyってこんなに便利だったんだ。
拡張コマンドを追加できるなんて知らなかったよ!

さて、setup.pyを読んでいきます。
まずはここ。

try:
from solace import scripts
except ImportError:
pass
else:
extra['cmdclass'] = {
'runserver': scripts.RunserverCommand,
'initdb': scripts.InitDatabaseCommand,
'reset': scripts.ResetDatabase,
'make_testdata': scripts.MakeTestData,
'compile_catalog': scripts.CompileCatalogEx
}


コマンドの実装はこんな感じ

from distutils.cmd import Command
class InitDatabaseCommand(Command):
description = 'initializes the database'
user_options = [
('drop-first', 'D',
'drops existing tables first')
]
boolean_options = ['drop-first']

def initialize_options(self):
self.drop_first = False

def finalize_options(self):
pass

def run(self):
from solace import database
if self.drop_first:
database.drop_tables()
print 'dropped existing tables'
database.init()
print 'created database tables'

わかりやすい!

もう一つ勉強になったのがtests_require

setup(
name='Plurk_Solace',
version='0.1',
url='http://opensource.plurk.com/solace/',
license='BSD',
author='Plurk Inc.',
author_email='opensource@plurk.com',
description='Multilangual User Support Platform',
long_description=__doc__,
packages=['solace', 'solace.views', 'solace.i18n', 'solace.utils'],
zip_safe=False,
platforms='any',
test_suite='solace.tests.suite',
install_requires=[
'Werkzeug>=0.5.1',
'Jinja2',
'Babel',
'SQLAlchemy>=0.5',
'creoleparser',
'simplejson',
'webdepcompress'
],
tests_require=[
'lxml',
'html5lib'
], **extra
)

ここで、テストのときだけ必要なライブラリを書くことができる。
python setup.py test
とすると依存ライブラリをとりにいきます。
すばらしい!


setuptoolsをもっと勉強します。
deploy+setup Scriptとして、今後活用していくことを誓います。

2009年8月6日木曜日

eclipse WTP encoding

eclipse


WTP encoding

最近、Javaの仕事が増えたせいでEclipseをよく使います。
WTPのencoding設定で困ってほかのプラグインに移ってしまう人が多いので、
まとめておきます。

その1. JSPがISO-8859-1になってしまって困っちゃう
Window => Preferences => General => Content Types
より
Text => JSP を選んで"Default encoding"にUTF-8を指定し、Updateを押す。


その2. HTMLがShift_JISに決め打ちされて困っちゃう。
eclipse.iniに
-Duser.language=en
と書いておく。

mercurial

http://www.vectrace.com/mercurialeclipse/
ためしにインストールしてみたmercurialプラグインは比較で文字化けしてしまい、
使い物になりません。

http://bitbucket.org/mercurialeclipse/main/
bitbucketが使われているのならば、対応は私がしようと思って、最新版をとってみたら
あれれ?使える。
というわけで、しばらくmercurial用クライアントとして、Eclipseも使ってみようと思います。

しかし、他のクライアントを使ってみるとわかるのですが、
Subversiveは本当によくできている。
Syncronize with Repositoryが本当に使いやすい。
あれをmercurialに使えたらどれほど幸せか。
まあ、ほしい機能はIssuesに全て登録してあるので、生ぬるく見守ります。

merge

分散型にした一番のメリットはマージ。
多人数で開発するときは、最初はpush --forceを使ってもらって、headを作りまくってもらう。
あとはこちらで全部マージできる。これは安心だ。
でも、この方式ならdarcsのほうがいいのかなー。

まだマージのBest Practiceを見つけられない。
試行錯誤の連続だけど、ツールとしてはextdiff=WinMergeが今のところ楽かな。

2009年6月19日金曜日

Java detect Encoding for Japanese

javaにはエンコード自動認識が標準ではなかったので、探し回った。
* jchardet
* juniversalchardet
などが見つかったけど、とりあえず、標準で逃げる方法。


private final List<CharsetDecoder> decoders;
{
// 色々試した結果、この順序が必須
String[] names = new String[] { "ISO-2022-JP", "EUC-JP", "UTF-8",
"windows-31j" };
decoders = new LinkedList<CharsetDecoder>();
for (String name : names) {
decoders.add(Charset.forName(name).newDecoder());
}
}
public Charset detectEncoding(byte[] bytes) throws Exception {
for (CharsetDecoder d : decoders) {
try {
d.decode(ByteBuffer.wrap(bytes));
} catch (CharacterCodingException e) {
continue;
}
return d.charset();
}
throw new IllegalArgumentException("デコードできませんでした。");
}

public void testDetectEncoding() throws Exception {
String[] samples = new String[] { "平", "カ", "1", "ひ", "b", };
for (String s : samples) {
assertEquals("windows-31j", detectEncoding(s.getBytes("sjis")).toString());
assertEquals("UTF-8", detectEncoding(s.getBytes("utf-8")).toString());
assertEquals("EUC-JP", detectEncoding(s.getBytes("euc_jp")).toString());
assertEquals("ISO-2022-JP", detectEncoding(s.getBytes("jis")).toString());
}
}

2009年5月25日月曜日

timeit on java

SpringのAutoWireの有無による速度差と、CGLIBとProxyの速度差を調べたくなった。
AutoWireの有無による速度差はなし。
CGLIBとProxyも十分無視してよい速度差。

上の目的のためにtimeitみたいに、適当な回数分だけループして時間を計測してくれるツールを作った。
一定の時間を越えるまで回数を増やしてループし続けます。
最初は500msにしたら、テストスイートが終わらなくなったので、50ms。
比較用に交互に実行する機能があるといいかも。

public abstract class Timeit {
protected String name;

public Timeit(String name) {
this.name = name;
}

public void timeit() {
int before = 0;
int count = 1;
long sum = 0;
prepare();
while (true) {
int loop = count - before;
long start = System.nanoTime();
for (int i = 0; i < loop; ++i) {
this.invoke();
}
long end = System.nanoTime();
long current = end - start;
sum += current;
if (current > 50 * 1000 * 1000) {
break;
}
before = count;
count = count * 2;
}
long time = sum / count;
System.out.print(name + ": ");
if (time < 1000) {
System.out.printf("%dns\n", time);
} else if (time < 1000 * 1000) {
System.out.printf("%.3fμs %dtimes\n", time / 1000.0, count);
} else if (time < 1000 * 1000 * 1000) {
System.out.printf("%.3fms %dtimes\n", time / 1000.0 / 1000, count);
} else {
System.out.printf("%.3fs %dtimes\n", time / 1000.0 / 1000 / 1000,
count);
}
}

public void prepare() {

}

public abstract void invoke();

public static class TimeItTest extends TestCase {
public void testSimple() throws Exception {
new Timeit("simple") {
public void invoke() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
}
}
}.timeit();
}
}
}

Mockito

Mockito便利です。
jMockの変態構文に慣れない方は、EasyMockを使ってらっしゃると思います。
EasyMockのスタイルは素敵ですね。
ただ、人に勧めるときに、replayとかverifyとか、定義以外に文をはさむのってなんかいやですよね。
お勧めしづらいですね。
expectLastCallなんかもみっともない。

そこでMockito


// モック作成
@SuppressWarnings("unchecked")
List mockedList = mock(List.class);

// あらかじめ、何が起こるかを書く
when(mockedList.add("one")).thenReturn(true);
// voidはwhenの引数として受け取れないのでdo*で書き直す。
doNothing().when(mockedList).clear();

// モックに対して実行
assertEquals(true, mockedList.add("one"));
mockedList.clear();


事前に定義する方法のほかに、あとからVerifyする方法もあります。

// モック作成
@SuppressWarnings("unchecked")
List mockedList = mock(List.class);

// モックに対して実行
mockedList.add("one");
mockedList.clear();

// 検証
verify(mockedList).add("one");
verify(mockedList).clear();


呼び出し回数も限定しておきます。

@SuppressWarnings("unchecked")
LinkedList mockedList = mock(LinkedList.class);
// モックに対してメソッド呼び出し
mockedList.add("once");

mockedList.add("twice");
mockedList.add("twice");

mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

// 回数指定で検証開始
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

// 呼ばれてない確認。times(0)でもOK
verify(mockedList, never()).add("never happened");

// 細かい数は不要。一度でも呼ばれたらOK
verify(mockedList, atLeastOnce()).add("three times");


すでに存在するオブジェクトに対して、部分的にモックを割り当てます。
名づけて"spy"

// 部分的に書き換える。
LinkedList spyList = spy(new LinkedList());
when(spyList.size()).thenReturn(100);

// addやgetは本来の動き
spyList.add("one");
assertEquals("one", spyList.get(0));

// sizeは置き換えました。
assertEquals(100, spyList.size());

verify(spyList).add("one");


Mock作成が楽しくなりました。
Javaの構文による呼び出し順序を利用したマジックですが、
ここまできれいな構文で書けるなら、受け入れるしかないでしょう。
Mock内で引数に加工したり、詳しく検証する場合は、Anythingというインターフェースを使って実装するのですが、そこはまだありがちな構文。

PowerMockもMockitoに対応中。
まあ、PowerMockなんて、一時的に使うことはあっても、すぐ書き直してMockitoだけで通るように変更するのでいらないけど。

2009年4月8日水曜日

How Fast is JRuby

How JRuby Makes Ruby Fast

def tak x, y, z
if y >= x
return z
else
return tak( tak(x-1, y, z),
tak(y-1, z, x),
tak(z-1, x, y))
end
end

tak(24, 16, 8)を10回分の時間はいかほど?

user system total real
0.524000 0.000000 0.524000 ( 0.524000)
0.338000 0.000000 0.338000 ( 0.338000)
0.325000 0.000000 0.325000 ( 0.325000)
0.299000 0.000000 0.299000 ( 0.299000)
0.310000 0.000000 0.310000 ( 0.310000)

JRubyはええええええ。
と思ったけど、Rubyの速さをよく知らないので、わからない。
あまりベンチマークに興味を持ったことがない。

一応、手元のJRubyでも試したよ。
jruby --server --fast tak.rb

user system total real
1.281000 0.000000 1.281000 ( 1.219000)
0.906000 0.000000 0.906000 ( 0.906000)
0.906000 0.000000 0.906000 ( 0.906000)
0.922000 0.000000 0.922000 ( 0.922000)


とりあえずPythonと比較してみた。
ここはさくっと1秒をきってもらいたいところ。

def tak(x, y, z):
if y >= x:
return z
else:
return tak(tak(x-1, y, z),
tak(y-1, z, x),
tak(z-1, x, y))

python tak.py

4.68799996376s
4.67199993134s
4.71900010109s
4.70300006866s

。。。

気を取り直して、Jython。
まあ、期待してないっす。Pythonの倍ぐらい遅くてもぜんぜん気にしないっす。
だって、お金かかってないっすからね。
Pythonと同じソースが走るのは優秀っすね。
jython tak.py

12.2509999275s
12.1879999638s
12.2030000687s
12.1879999638s

。。。


Ironのじつりきに期待したいけど、ビルドしたことなかった。
いつか誰かがやります。


ところでさ、Javaだとどんなもんかな。
0.3秒が数倍遅いって言ってるんだから、0.08秒ぐらいか。
そりゃはやいね。

static int tak(int x, int y, int z) {
if (y >= x)
return z;
else
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
}

java Tak.java

0.107289151s
0.107325477s
0.107177706s
0.10697179s

JRubyはえええええ!
しかし、ここはJavaのすごいところが見たい!
java -server Tak.java

0.094475588s
0.081646268s
0.081483518s
0.08147525s

やっぱりJRubyはええええ。
ええい、他にオプションはないのか。

というわけでJRubyの速さを際立たせるだけでした。
あ、そうだ。
Dubyが早いならGroovyも型を指定すれば早いんじゃね?
cp Tak.java Tak.groovy
groovy Tak.groovy

6.369433797s
6.266855432s
6.264844057s
6.277156358s

本気か?
事前にコンパイルしないとだめか、そうかそうか。

groovyc Tak.groovy
groovy Tak

6.336249071s
6.235964946s
6.235621352s
6.233301473s

ち、ちょっと早くなったのかしら。

型を指定してあげてるのにこんなに遅いって、Groovyは駄目な子なのか。
ちなみに型を指定しなかったらJythonより遅いのか。
SpringSourceしっかりしろよ。
怖いもの見たさでチェック

def tak(x, y, z) {
if (y >= x)
return z;
else
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
}


3.599673454s
3.504575443s
3.504178545s
3.503208614s

おおおおおお!どうなってるんですか?
速くなりました。
倍近く早いよ。
ってか、Pythonより速い。
Groovyやるな!
ってか、なんで?

ここまできたらScalaも試そうか。

def tak(x: Int, y: Int, z: Int): Int = {
if (y >= x) z else tak( tak(x-1, y, z),
tak(y-1, z, x),
tak(z-1, x, y))
}

scala Tak_scala

0.191174899s
0.185557729s
0.18544463s
0.185426475s

思ったより少しだけ遅いけど、悪くないね。
serverモードはないのかな?


というわけでGroovyの変態さが伝わったかな?
本当はIokeでも試したんだけど、まったく答えが返ってこないのでなかったことにしました。

このまま終わると晩御飯をおいしく食べられないので、Pythonでもうチョイがんばります。
まずはご存知psyco

import psyco
psyco.full()

と先頭につけるだけ。

0.281000137329s
0.296999931335s
0.296999931335s
0.281000137329s

おお、いいね。JRubyに勝った!

つづいてCython

cdef int ctak(int x, int y, int z):
if y >= x:
return z
else:
return ctak(ctak(x-1, y, z),
ctak(y-1, z, x),
ctak(z-1, x, y))


0.155999898911s
0.171999931335s
0.156000137329s
0.155999898911s

よし、Scalaに勝った!


やっぱりPythonだね。

ごらんのスポンサーの提供でお送りしました。
java version "1.6.0_11"
Scala code runner version 2.7.3.final
jruby 1.3.0 (ruby 1.8.6p287) (2009-04-08 r)
Jython 2.5b1 (trunk:5903:5905, Jan 9 2009, 16:01:29)
Python 2.6.1
Groovy Version: 1.6.1

TODO
グラフを貼る


結論。
Groovyは型を指定すると遅くなる。

2009年3月31日火曜日

sax with HTMLParser

expatでShift_JISが扱えればベストなのですが、どうにもならない

<?xml encoding="Shift_JIS"?>

これが書いてあると引っかかるので、本文をutf-8にして回避することもできない。
んがー、どこもめ。
一行目削るしかないかなー。

2.6のxmlパッケージにはexpat以外のXMLパーサがないのが不思議。
xmllibはdeprecatedついてるけど、パーサはまだ生きている。
ココから引っ張るか、あるいはmarkupbaseからがんばるかな。
xml.sax.saxutils.XMLGeneratorを使いたいから、頭が痛い。

saxの構文は面倒くさいし、sax風のXMLFilterを書くのが一番早いかも。

つらい。
意外とPythonは不便。
Jythonに乗り換えたくなってきた。


あと、なんか知らんけどRailsの仕事が舞い降りてきそうだ。
すべて明示的に書いてきたPython文化から、暗黙文化に移るのはかなり抵抗がある。
でも1.9とcapとmerbには興味があるので、ひさしぶりにRuby触るのもいいのかもしれないな。

2009年3月27日金曜日

Real World Django

Real World Django

LJWorldcom運営における体験談か。
Modelをハードコーディングするなとか、拡張性にまつわる話。
スケーリングや構成にまつわる話など、興味深い。
PDFだけじゃなくて、お話も聞きたいなー。
Podcastとか公開してほしい。


昨日、駅前でホットペッパーの人が武富士の人に怒られていた。
何をしたのか知らんけど、駅前ばら撒きの一日の長というやつか。
けんかするなら端っこでしてほしかった。

2009年3月18日水曜日

parrot 1.0.0

http://www.parrot.org/news/2009/Parrot-1.0.0

きっと何かが起こっていると思うけど、何が起こっているのかわからない。
早くつながるといいなー

2009年3月12日木曜日

Seasar2

ためしてみた。
軌道に乗れば確かに結構早いですね。
HotDeployすばらしい。
しかし、開発はGroovyで進めて、
後にテストしながらJavaに移行すればHotDeploy同然なのではないかと思うのです。
ずっと書きやすいし。
Groovy大好きです。

サクサクとそこらじゅうに書いてあるけど、サンプルがちっとも見つからない。
さくさくならそれこそDjangoのように掃いて捨てるほどサンプルが出るはずなのに。
最初のマニュアルはあるけど、何もわからない人向けと、用語がわかる人のどっちつかずで
中途半端。
非常に読みづらい。
だれもサンプルを作ってくれなくても、サクサクならBlogやWikiぐらい作っておいておけばいいのに。

たとえば、EclipseのPluginで必要なものは何かを一望できる場所がない。
JEE対応Eclipse3.3以降で、
・Kijimuna
・Dolteng
・SAStrutsPlugin
・PropertyEditor
が必要と、さっと書いてあれば進めるのに、インストールの仕方を逐次読まなければならない。
初心者に親切だと思うかもしれないけど、書いてあるとおりに進めても完成しない。
突然、状況にあわせて書き換えろと書かれる。

どうせならEclipseもDIもわかる人にSeasarはどう便利なのか訴えるページを書けばいいのに。
ない。


Doltengで作ったプロジェクトはFreemarkerでコンパイルエラー発生しますね。