« 2007年06月 | メイン | 2007年08月 »

2007年07月29日

Djangoのキャッシュはすごいな日記

Djangoテンプレートで特定のメソッドを引数付きで呼びたいという話。 {% apply func arg1 arg2 %}という書き方をするとfuncがcallableであるときに 自動的にcallされてしまうので{% apply "func" arg1 arg2 %}にしなければいけない、 とこの前書いた。

しかしこの方法ではfuncが文字列であるので、 下のような書き方をサポートする場合、 evalの名前空間にテンプレートのレンダリングに 使われているコンテキストを渡す必要がでてきてややこしいことになる。

{% for comment in comments %}
  {% apply "comment.get_something" user %}
{% endfor %}

ふと気がついたのだけど、関数を返す0引数関数を作ればいいだけじゃないかと。 comment.get_somethingが引数を1個とる関数を返すのなら {% apply comment.get_something user %}でも問題ないのでは。


= ユニコードブランチ、すごいな。 セッションIDまでユニコードか…。あたりまえか…。 「全てがUになる。」(ぇ)
= Star Alliance Japan - 運賃案内。 世界一周チケット(1年間有効)。 途中でイギリスに1ヶ月滞在して大英博物館に入り浸りとかも可能。 結婚して子供ができるとこういうことはできなくなるだろうから、 やるのは今のうちなのか。

一周で100万という話だったのだけど、エコノミーならもっと安いね。 もちろん飛行機代なんかよりも宿泊費の方が高いことをお忘れなく。 1年間ホテル暮らしなわけだもんなぁ。


= なんか昨日も今日も9時頃になると眠くなる。 帰ろうか…。
= 5時に目が覚めると、反射的に二度寝をしてしまうのをなんとかせねば。 せっかく5時に起きたのに。
= 世界一周は飛行機代よりも ホテル代が高いという話だけど、 日本のホテルが高いと仮定してカプセルホテル4000円×365日=150万くらい。 んー。高いなー。

世界一周という言葉には萌えるけども、 細かく何度か行く方がいいのかもなぁ。 冬の地中海と夏の地中海は別物だしな。


= b-mobile、あと80日くらい残っている。 金額で言えば2~3万円分は残っている。 でも遅い。
= 紙粘土で作った魚眼レンズとトルソーは 乾いたらでこぼこになった。 紙粘土は水気がなくなると変形するから、 こういうのはパテとかを使うらしい。
= 暑いなー。 冷房つけるしかないのかな。

シャワーを浴びたら涼しく感じるようになった。 表皮の血管が冷えて収縮しているせいで 熱を捨てられず、こもったように暑くなっていたのだろう。

今まで眠くなかったのだけど、熱が捨てられ始めると眠く感じる。


= スタイルシートはまず最初にresetしないといけない。

あとresetしたせいでDIVのマージンがなくなってツメツメになっているのを、 テンプレートいじって修正しなきゃと思っていたけど、 普通にスタイルシートにdev { margin: ... }とか書けばいいだけだと気づいた。

resetしたせいで文字がすごく小さくなったのでfont-size: 16pxとかやったせいで、 フォントサイズの変更ができなくなってしまったらしい。

月曜日はその2点のスタイルシートを修正する。 あと最近もっさり感の出てきた言語一覧のページにキャッシュ機能をつけよう。 Django のキャッシュフレームワーク : Django オンラインドキュメント和訳

Djangoのキャッシュ機能、最初ものすごく斜め読みをして 「ページ単位とかサイト単位でキャッシュしたいんじゃなくて、 統計情報の計算結果だけどこかにためておきたいだけなんだけどなぁ」 なんて考えていたけど、 きちんと読んでみたら「低水準API」って章にちゃんとそれの方法が書いてあった。 低水準っていうからめんどくさいかと思いきや:

>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
うわー。簡単すぎる。30ってのが秒数らしいので状況に応じて86000とかすればいい。

で、明示的にdeleteすることもできる。 時間のかかる統計計算の結果をキャッシュするために、 「えーと、結果を保存するテーブルを作って、変更が起きるような処理のところで再計算してテーブルに入れて…」 なんて考えていたけどそんなことは不要。 いま統計処理をやっているところで cache.get('my_key')してNoneの時だけ再計算するようにし、 統計結果が変化するような処理をしたときには単にdeleteすればいい。

投票に関する統計結果は、投票と同時に更新すると他の情報とのかねあいで 「誰がどれにどういう投票をしたか」が透けて見えてしまうケースがあるので、 どうしようかと悩んだりもしていたけども、これも簡単。 投票時にdeleteしないでキャッシュの生存時間を5分とかにしておけばいい。

バックエンドをmemcachedにしたりデータベースにしたり、ファイルにしたり、など色々選択肢があるみたい。 新しいサーバが来たらmemcachedとか入れて試してみようっと。


= PHSでどう書くorgを見るのが辛いので、 画像やCSSやJSをローカルにキャッシュさせようと思う。 CSSやJSはたまに変わるけども、 画像は変わらないので簡単。 で、ちゃんと期待したとおりに動いているかとか見るためには、 リクエストとはレスポンスヘッダとか見れるようにしたいなぁ。 と思って調べたら ネットランダム - これであなたもはまちちゃん!! 「リクエストとレスポンスヘッダをいつも目に入るようにするにはFirefox拡張のLiveHTTPHeadersを使うといいよ!」 だって。

http://ja.doukaku.org/

GET / HTTP/1.1
Host: ja.doukaku.org
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.5) Gecko/20070713 Firefox/2.0.0.5
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: (たぶん載せない方がいいんだと思うんで割愛)

HTTP/1.x 200 OK
Date: Sat, 28 Jul 2007 17:49:49 GMT
Server: Apache/2.2.3 (Fedora)
Vary: Accept-Language,Cookie
Content-Language: ja
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
q=0.5って何だろう…。

ヘッダに「Accept-Language: ja,en-us;q=0.7,en;q=0.3」って入っているから日本語版が表示されるんだな。 たぶんjaをAcceptしなければ英語版が出るんだろう。 英訳適当なところがあるから直さないとなぁ…。

「Vary: Accept-Language,Cookie」で、間のプロクシとかがキャッシュするときに 他人のページが出たりしないようにしているって何かに書いてあった。

「Connection: close」 って、closeしてもいいんだろうか。せっかく「Connection: keep-alive」なのに??

http://ja.doukaku.org/static/style/style.css

GET /static/style/style.css HTTP/1.1
(略)
If-Modified-Since: Fri, 27 Jul 2007 09:40:00 GMT

HTTP/1.x 304 Not Modified
Date: Sat, 28 Jul 2007 17:49:52 GMT
Server: Apache/2.2.3 (Fedora)
Connection: close
Vary: Accept-Language,Cookie
ほー。 ここにExpiresを返すようにすれば期限切れまではローカルのキャッシュを使うようになるんだな。

もしかすると ビュー単位のキャッシュ で/static/images以下へのアクセスを@cache_page(86400 * 365)とかやるだけで終了かも。

開発環境をノートに入れてくるの忘れたので実際にやるのは月曜日。


= 日曜の朝。 せっかくレスポンスヘッダが見られるようになったのだから、 画像がいっぱいあってしかも負荷対策もきちんとやってそうなサイトを見る。 ミクシィ。
HTTP/1.x 200 OK
Server: Apache
Last-Modified: Fri, 15 Dec 2006 07:17:47 GMT
Etag: "2e0828-9a-6dc6fcc0"
Accept-Ranges: bytes
Content-Length: 154
Content-Type: image/gif
X-Cache-Lookup: HIT from banta.mixi.jp:80
Cache-Control: max-age=520596
Expires: Sat, 04 Aug 2007 01:45:21 GMT
Date: Sun, 29 Jul 2007 01:08:45 GMT
Connection: keep-alive
そうだよなぁ、やっぱり「Connection: keep-alive」って返ってないとだめだよなぁ。 どう書くorgはなんでcloseを返してるんだろうなぁ。 「Expires: Sat, 04 Aug 2007 01:45:21 GMT」で5日後を返している。

「X-Cache-Lookup: HIT from banta.mixi.jp:80」とか 「Cache-Control: max-age=520596」 とか「Etag: "2e0828-9a-6dc6fcc0"」は何だろうなぁ。

Expiresのネタもと: Kazuho@Cybozu Labs: キャッシュの上手な使い方。 とりあえず画像と全てのページで読み込まれているCSS、JSを1年先にExpiresするような設定にすれば だいぶ軽くなるんではないかと思っている。 ファイル名変えるのも簡単だしねぇ。 昔の「たくさんのHTMLにコピペで同じ内容がたくさん」なんて時代と違って Djangoテンプレートがツリー状の継承構造になっているから、 根本を1カ所書き換えるだけでいい。 ファイルの名前に書き方の規則をあらかじめ決めておけば(例えばjquery-0.0.0.jsとか)

(?P<filename>.*?)(-\d+(\.\d+)*)?\.(?P<ext>\w+)
ってな感じで機械的にバージョン番号を捨てたりできるから 実際のファイルのファイル名を書き換える必要すらない。

Cache-Control: max-ageとSquid : blog.nomadscafe.jp

2007年07月22日

est! est! est!な日記

Test! Test! Test!

どう書くorgのテストに30分もかかってしまう事態を何とかしたいのだけど、 そもそも30分かかるのが当たり前のことなのか異常な事態なのかそれがわからない。


= 最近ライブドアリーダーを使い始めたのだけど、 Spaceキーを押しても1ページスクロールしてくれないのに困って、 ソースコードを読んでみた。 reader_main.jsの2005行目:
        scroll_page: function(num){
                var h = $("right_container").offsetHeight - 40;
                var c =
                        (Config.scroll_type == "page") ? h:
                        (Config.scroll_type == "half") ? h / 2 :
                        (Config.scroll_px || 100);
                $("right_container").scrollTop += c * num;
        }, 
1ページスクロールのオプションがあるみたいだなぁ。 と思って設定画面に行ったら詳細設定のところにスクロール量の設定があった。

なんか順番間違ってる気がした。


= ノートパソコンで30分かかったテストはラボマシンに移動してやったら2分かからなかったorz

リンク切れテストまでかけても3分程度だ。


= 日記あまり書いてなかった。 views.pyを切り分けた。 今までのコードがほとんど__init__.pyに入っていて、 コメント投稿部分だけ別のファイルに切り出した。 いままでmeadowでC-x C-f vi[TAB]と押してビューを出していたのに、 それじゃダメになった。面倒だぞ。

モデルのリファクタリング方法に関して、 テーブルの名前が変わってしまうのでmodelsを 複数のファイルに分割してimportするという手は使えない。 (自分でテーブルの名前を変更して回るのなら使える)

で、よく考えたらテーブルの定義と、ユーティリティ関数が同じところにある必要はないので models.pyはテーブルの定義を入れるところと見なし、 ユーティリティを別ファイルに切り出す形で整理することにした。

いま、例えばUserオブジェクトのabsolute_urlは下のように 「後からメソッド挿入」で実装されている。 こういうのを別のファイルに切り出すと言うこと。

def get_absolute_url(self):
    return '%s/user/%s/' % (settings.URL_BASE, self.id)
User.get_absolute_url = get_absolute_url
Commentみたいな自分で定義したクラスは、 普通にクラスのメソッドとして実装されているけども、 よく考えるとフィールドの定義と動作の定義が同じ場所にあるのも不自然。
= 思うんだけども、 どうやら僕はPythonスクリプトが大規模になったときにどう切り分けるかというノウハウが足りないような気がする。 クラスの定義と、そのクラスに動的にメソッドをつっこむところを分けたい。 そしてパスとかややこしくなるのが怖いのでexecfileじゃなくてimportで何とかしたい。
class Foo:
    pass

Foo.x = 1
class Foo:
    pass
Foo.x = 1
に分けたい。 そしてフレームワークの都合で呼び出されるのはクラス定義のある側なので、 メソッド追加側は何らかの方法でFooへの参照を獲得する必要がある。 しかしここでimportとかするわけにはいかない(よね?)

根本の側から参照を渡してやる。

class Foo:
    pass

from bar import wrap
wrap(Foo)
これで原則的にはできる。 で、問題は複数のクラスがあっておたがいに参照しているケースなのだけど、 それはglobals()を渡して必要なものだけ取ってもらえばOK。

これでいいのかなぁ。 いっそglobals().update(given_globals)でもOKだが…。


= 投稿フォームに?parent=parent_idって感じでGETで親コメントのIDを渡していたが、 フォームのactionにもそれを入れておかないとフォームのバリデーションエラーで やり直しになったときにparentが失われてしまう。
= 土曜日。 結局今週もちっとも原稿が進まなかったので、 秋葉原の喫茶ルノワールに来ています。
= models.pyのリファクタリングの話の続き。 models.pyはモデルの定義なのだから、 models.pyの例えばCommentの定義に、 モデルの他の要素、たとえばRatingの関係する処理を書くのはおかしい。 その思想には同意できる。

しかし現実的な問題として 「コメントに付いているレーティングのうち値が0でないものの 値の合計を、その個数の合計でわった値を求めたい」 というときに、そのコードをどこに書くべきか。

テンプレートであまり複雑な処理をすべきではないし、 そもそもテンプレートでは書けないような処理もある。 テンプレートで無理矢理やるために、それに特化したテンプレートタグを作るのもおかしい。

ではビューでやるのか。 この場合問題になるのは、 テンプレートの中でどういう形でCommentが表示されるかビューにはわからないところだ。 ビューであらかじめ計算した結果とCommentオブジェクトと結びつける方法がない。

単にforで順番に表示などで事前にわかる場合は コメントのリストと計算値のリストを渡すとか、 コメントと計算値を辞書でくるんでリストを作って渡すとかで なんとかなるかも知れないが、 regroupやテンプレートの再帰呼び出しが使われる場合はそうも行かない。 本来MVCのVが担当すべき「どういう順序で表示するか」という情報まで、 全部Cで頑張ってしまうハメになる。 これはおかしい。

結局それはテンプレートが関数を明示しないでも評価してしまうところに問題がある。 Ruby的なんだな。 Pythonでxs.uniqと書けば、それはxsというオブジェクトのuniqというメンバで、 それが関数かどうかに関係なくそのオブジェクトが取得できる。 しかしRubyでxs.uniqと書けばこれはxsがArrayの時にはuniqがメソッドなので 勝手に呼ばれてしまう。xsが別のオブジェクトで、 例えばuniqがtrue/falseの値を取るようなものならもちろん呼び出さない。 おかげでRubyではメソッドをオブジェクトとして受け渡しすることが難しいので、 シンボルという文字列のようなものを使う羽目になる。 Djangoのテンプレートもそれと同じで、もしPython的なことをしようと思ったら 文字列で渡すしかない。apply_funcなんてカスタムフィルタを作ってこうやるしかないだろうか→ {{ comment|apply_func:"calc_rating" }}

もしくは{% apply_func "is_user_already_rated" comment user %}か?

しかし、この方法では apply_funcから見える位置に、 どのオブジェクトを引数に取るかと無関係に、 ずらずらと関数が並ぶことになる。

やはり上で書いたような方法で 「モデル定義のmodels.py」の他に 「モデルにまたがる演算を書く場所」を作るのがいいのではないかと そういう結論にしようと思ってつらつら書いてきた。 一引数の関数はレシーバでその引数を渡せるので テンプレートから{{ comment.calc_rating }}と呼べるし。 でも、二引数以上のケースを考えるとそれで解決とも言い難いなぁ。 やっぱり {% apply_func "is_user_already_rated" comment user %} と書けるカスタムタグが必要なのだろうか。


= そんなことを書くためにわざわざ喫茶店に来ているわけではない。 原稿を書かなければ。
= 隣の席の人が自信たっぷりに「ハムニカ構造」とかいうから 「ハニカム構造だっ!」とつっこみたくてしょうがない。
= いま、ファミコン時代の電子音を聞くと 懐かしい感じがして逆に面白いのと同じで、 あと10年くらいしたらMIDIの曲が逆に面白くなるのだろうか。 20世紀のカラオケみたいとか。
= ルノワールの水出しコーヒーはおいしいと思う。
= 壁に向かって作業しているのだけど、 壁に鏡があるので後ろが見える。 後ろの席のおじさんがまじめな顔でエロDVDの裏の説明を読んでる。 帰ってゆっくり読めばいいのに。

家には安息の場所がないのだろうか。

こわいこわい。


= RubyはPythonよりもピュアなオブジェクト指向なのかも知れないが、 世の中の大半の人は奥底がピュアかどうかより外見がそれっぽいかに気を取られる。 Pythonのオブジェクト指向がJavaと比べて「中途半端なオブジェクト指向だ」だなんて、 どこをどう勘違いしたのやら(見かけにだまされているとしか)

詳しく知らないけどオブジェクト指向度はこんな感じ?

Smalltalk >> Ruby > Python >> Java > Perl >> 機械語

= amachangが「数ヶ月前の自分のコードは汚くて捨てたくなる」みたいなことを言っていたけど、 僕のJython本の原稿に出てくるコードはcamelCaseで書かれていて全部修正したくなる。 PEP8読めよ昔の自分!

最近のコードは全部splited_with_underscore。


= 人が減ってきたなぁ
= Pythonってクラスベースなのかな。 それともプロトタイプベースなのかな。 定義って明確にあるものなのかな。
= 色々調べ物がしたいが、 ネットにつなぐためだけに永田町まで行くのもなんかなんなんだな。

はっ、これはマンガ喫茶メソッド?!

いやまて、 マンガ喫茶に行ってもラボに行っても、 まともに眠れないのは同じだ。 選択肢としては 「マンガ喫茶に行って帰れる時間で帰る」と 「ラボに行って帰れる時間で帰る」と 「ラボで泊まる」の三択だ。

たぶん泊まるとぐだぐだになって明日も潰れてダメだろうな。 ここは8時までだから2~3時間マンガ喫茶に行く方針かなぁ。


= はっ。 カプセルイン秋葉原という手があったか! 無線LAN完備で、個室で、入浴設備があって、眠れる。 天井が低くて腰に悪いのが難点だが…。

あっ、前の冬山Pythonキャンプに誰かが持ってきていた座椅子とか買えばパーフェクト?! 誰が持ってきていたのか忘れた…。

とりあえずお腹空いた。 ルノワールは20時までかと思ったけど、そうでもないみたい。 でもお腹空いた。

今日はだいぶはかどった。 具体的に言うと…3キロバイト…がっくり。 たくさん書いた気がしたのに1500文字か。原稿用紙4枚か。 道のりは遠いなぁ…。

冷静に考えよう。 選択肢は「マンガ喫茶に行って2時間で帰る」と 「カプセルインで寝るまで書く」の二択。 カプセルインは3000円だったかな。 マンガ喫茶は8時まで1500円+延長30分500円とかかなぁ。 まったく相場がわかっていない。 とりあえずサーベイに行こう。

ルノワールは総武線と山手線の周りにあるらしい。 錦糸町にはなかったっぽい。

マンガ喫茶はルノワールの上の階にあった。 最初の1時間が400円、2時間で880円、 ナイトパックが6時間980円だったかな。 0時に入って朝の6時に出られるはずがないので、 9時まで居て2200円、11時までで3000円か。 だったらカプセルインといい勝負かも。 カプセルインより座り心地がよくて寝心地が悪い。


= ヨドバシカメラはまだ空いているのか。

ヨドバシカメラの上のカレー屋って「コバラヘッタ」って名前なのか。

カレーを食べて「そういえばルノワールにもYahooBBのマークがあったなぁ」 と1Fのマークを眺めていたら店員にキャッチされる。

今使っているb-mobileが切れたらe-mobileに乗り換えることに決まった。


= カプセルイン秋葉原。4000円。 午後五時から入れて翌朝十時チェックアウト。 がら空き。

エアコンの音がひゅるひゅるする。 消音ヘッドホン持っててよかった。

このマシンは一度ワイヤレスLANに電源が入ったままサスペンドしてしまうと、 再起動するまでつながらなくなる。 よって再起動。


= 再起動によって画面から原稿を開いていたテキストエディタが消えたせいで ぐだぐだに。だめじゃん。
=
>>> function counter(){this.push = function(){this.count += 1;x}; this.count=0}
>>> x = new counter()
Object count=0
>>> x
Object count=0
>>> x.push()
>>> x
Object count=1
できたオブジェクトがcounterへの参照を持っていないのが気になる。
{package Counter;
    sub new {
        my $pkg = shift;
        my $hash = {
            count => 0,
            name => shift,
        };
        bless $hash, $pkg;
    }
    sub push {
        my $self = shift;
        $self->{count}++;
        print $self->{name}, $self->{count}, "\n";
    }
}

$counter = Counter->new("car");
$counter->push();
$counter->push();
$counter->push();
ふむ。

Perlの場合、パッケージに複数の関数が入っている、という仕組みがあらかじめあって、 そこに「インスタンスごとの値が入る名前空間(ハッシュ)を貼り付ける」 という方法でオブジェクト的なものをつくったと言うことか。

function counter(name){
    this.push = function(){
        this.count += 1;
        console.log(this.name + this.count)
    };
    this.count = 0;
    this.name = name;
}
>>> x.push()
car1
>>> x.push()
car2
>>> y = new counter("hoge")
Object count=0 name=hoge
>>> x.push === y.push
false
ふむ。
>>> function Counter(name){
    this.count = 0;
    this.name = name;
}
Counter.prototype.push = function(){
    this.count += 1;
    console.log(this.name + this.count)
};
function()
>>> x = new Counter()
Object count=0
>>> y = new Counter("hoge")
Object count=0 name=hoge
>>> x.push === y.push
true
なるほど。 クラスがないからコンストラクタと作られる オブジェクトを結びつけるものもないのか???
>>> def push(this):
...     this["count"] += 1
...     print this["name"], this["count"]
...
>>> def Counter(this, name):
...     this["count"] = 0
...     this["name"] = name
...
>>> Counter.__proto__ = {}
>>> Counter.__proto__["push"] = push
>>> def new(init_func, *args):
...     o = {}
...     init_func(o, *args)
...     o["__proto__"] = init_func.__proto__
...     return o
...
>>> new(Counter, "car")
{'name': 'car', '__proto__': {'push': <function push at 10317191>}, 'count': 0}
>>> counter = new(Counter, "car")
>>> def get_attr(o, name):
...     v = o.get(name)
...     if not v:
...         v = get_attr(o["__proto__"], name)
...     return v
...
>>> get_attr(counter, "push")(counter)
car 1
>>> get_attr(counter, "push")(counter)
car 2
>>> get_attr(counter, "push")(counter)
car 3
JavaScriptがやっていることをPythonで実装してみた。 ようするにこういうことか。 プロトタイプチェーンがないので、get_attrで実装。 counter.pushが、プロトタイプチェーンのあるget_attrで解決された後、 JavaScriptではレシーバをthisにいれるけど、 Pythonには暗黙のthisがないから直接渡す。
>>> def new(pkg, name):
	hash = {"count": 0, "name": name}
	return bless(pkg, hash)

>>> def push(self):
	get_hash(self)["count"] += 1
	print get_hash(self)["name"],
	print get_hash(self)["count"]

	
>>> Counter = {"package":{
	"new": new,
	"push": push,
}}
>>> def bless(pkg, hash):
	return {"package": pkg["package"],
		"hash": hash}

>>> def get_hash(obj):
	return obj["hash"]

>>> def arrow(pkg, name):
	return pkg["package"][name]

>>> counter = arrow(Counter, "new")(Counter, "car")
>>> counter
{'hash': {'count': 0, 'name': 'car'}, 
 'package': {'push': <function push at 0x0165AB70>, 
             'new': <function new at 0x0165ABB0>}}

>>> arrow(counter, "push")
<function push at 0x0165AB70>
>>> arrow(counter, "push")(counter)
car 1
>>> arrow(counter, "push")(counter)
car 2
>>> arrow(counter, "push")(counter)
car 3
Perlのx->{y}は->と{}の組み合わせではなくて単体の演算子だと判断したけど、 あっているかどうかは不明。
= そういえば書き忘れたけどPythonで書けばこんな感じ。
>>> class Counter():
	def __init__(self, name):
		self.count = 0
		self.name = name
	def push(self):
		self.count += 1
		print self.name, self.count

		
>>> counter = Counter("car")
>>> counter.push()
car 1
>>> counter.push()
car 2
上のJS版のこの2行は順序が違うな。 プロトタイプの中の値にinit_funcの中でアクセスできるもんな。
...     init_func(o, *args)
...     o["__proto__"] = init_func.__proto__
うんやっぱり。下のようにして確認できる。
>>> function init_func(){this.hello()}
>>> function hello(){console.log("hello")}
>>> init_func.prototype.hello = hello
hello()
>>> new init_func()
hello
Object
Pythonのクラスをクラスを使わずに実装
>>> def __init__(this, name):
	this["count"] = 0
	this["name"] = name

	
>>> def push(this):
	this["count"] += 1
	print this["name"], this["count"]

	
>>> Counter = {
	"__init__": __init__,
	"push": push,
}
>>> def instantiate(class_, *args):
	o = {}
	o["__proto__"] = class_
	class_["__init__"](o, *args)
	return o

>>> instantiate(Counter, "car")
{'count': 0, 'name': 'car', 
 '__proto__': {'push': <function push at 0x01EA94F0>, 
               '__init__': <function __init__ at 0x01EA9230>}}
ううむ。本質的にJavaScriptと同じような気がしてきた。 JavaScriptの場合は、 「初期化のための関数」が「プロトタイプ」と呼ばれる連想配列を持っていて、 そのプロトタイプの中に他のメソッドが入っている。 Pythonの場合は、 「クラス」が「名前空間」と呼ばれる連想配列を持っていて、 その連想配列の中に初期化のための関数と他のメソッドが入っている。 プロトタイプと呼ばれるものはないのでプロトタイプチェーンもないけども、 当然ながらクラスの名前空間は親へとチェーンしている。

Pythonって本当にクラスベースなんだろうか。 クラスベースとプロトタイプベースの違いって何なんだろうか。


= じゃじゃん。 Jython本の原稿の進捗を発表します! 現在の達成率、66%! ダメじゃん!全然ダメじゃん!月末までに終わらないじゃん! 今まで何やってたんだよ自分! どう書くorgやってる場合じゃないじゃん!

来週からは週に1回ペースで3週間発表がある。 ううむ。


= 段ボールで作った魚眼レンズアタッチメントVer.0.01は、 光軸が安定しないのでもっときっちり支えられるように紙粘土を買ってきた。 余った紙粘土は一応袋に入れてくくったけど、 どうせ次に使うときには乾いてしまっているからと思って、 何か作ってみることにした。

とりあえず胴体部分だけの塑像を作ってみた。 トルソーっていうのかな。 おおまかな曲線だけで人間を表現。 初めてにしてはまぁ悪くない感じにできた。 紙粘土だから表面にでこぼこ感があるのが残念だなぁ。 これをシリコンで型どりして硬質レジンでも流し込んで、 頑張って磨いたらきれいななモノができるかなぁ。


= 攻殻機動隊面白いなぁ。 終わった後のインタビューがさらに面白い。 今日はドリー&ズームという用語を覚えた。 ヒッチコックがよく使っていたらしい。

2007年07月18日

jQuery勉強日記

Ajax, Ajax, Ajax...
= 日本全国ラボめぐり:グループウェアの新しい使い方「ひとり言スレ」──サイボウズ・ラボ - ITmedia Biz.ID

もうちょっとマシな写真はなかったのかと問いたい…orz


= Ratingを使おうかと思っていたけど、 微妙に気に入らないところがあって、修正するよりシンプルなのを自分で作る方がいいと判断。
= jQueryに値を渡すためにDjangoのカスタムテンプレートタグ作っててちっともAjaxの勉強になってない!
$(".rating .button").click(function(){
  alert("hello");
  return false;
});
が動かないなぁと思ったら
$(document).ready(function(){
})
で包まないといけないのか…

お、動いた動いた。


= JavaScriptはデフォルトでグローバル変数なのでx = $(this); とか書けば$(this)の中身について Firebugのコンソールでxって書いて中身をインスペクトできる。
= なるほど。jQueryオブジェクトはDjangoのクエリセットみたいなものか。
for(var b in $(this).siblings()){
  b.removeClass("selected");
}
とかやって怒られたけど、$(this).sibling()は 「エレメント(にラッパかましたもの)の配列」じゃなくてクエリなんだな。 [0]でアクセスしたときにエレメントオブジェクトらしきものが返ったのでエレメントの配列かと思った。
  btn = $(this);
  btn.addClass("selected");
  btn.siblings().removeClass("selected");
これでOK。選択したものだけハイライト表示になって、残りのハイライトが解除される。

なおbtnの前にvarが付いていないのはインスペクトして遊ぶため。


= API/1.1.2/DOM/Traversing - jQuery JavaScript Library addのあるところにremoveもあると思うじゃないか。 removeはtraversingじゃなくてmanipulationなんだそうだ。 API/1.1.2/DOM/Manipulation - jQuery JavaScript Library
= Firebugの対話的インタプリタを使って、Pythonでコーディングしているときと同じように対話的にコーディング。
>>> btn[0]
<span id="rating_z_990" class="button selected" style="background-color: rgb(255, 255, 136);">
>>> btn[0].id
"rating_z_990"
>>> btn[0].id.split("_")
["rating", "z", "990"]
>>> bits = btn[0].id.split("_")
["rating", "z", "990"]
>>> ["", bits[2], "rating", bits[1], ""].join("/")
"/990/rating/z/"
>>> url = ["", bits[2], "rating", bits[1], ""].join("/")
"/990/rating/z/"
>>> $.get(url)
GET http://localhost:8000/990/rating/z/404 (78ms)
XMLHttpRequest channel=[xpconnect wrapped nsIChannel]
なんか想像以上に簡単だ。
= ウェブ魚拓 - Wikipedia。 へー。こんなサービスが。
= urls.pyにこんなの書いて
    (r'^comment/(?P\d+)/rating/(?P.)/$',
     views.comment_rating),
views.pyにこんなの書いて
def comment_rating(request, comment_id, value):
    print comment_id, value
    return HttpResponse(value)
作りかけのrating.jsにこんなの書いて
  bits = btn[0].id.split("_");
  url = ["", "comment", bits[2], "rating", bits[1], ""].join("/");
  $.get(url, function(data){
    alert(data);
  });
レーティング用のボタンをクリックするとalertデター! なんだAjaxって簡単じゃん(ぉ)
= get_or_create。データベース API リファレンス : Django オンラインドキュメント和訳
= あ、なるほど、TraversingのaddはDOMツリーへのエレメントの追加じゃなくて、 クエリへのクエリの追加なんだな。(なお用語が正しいかどうかは知らない。)
API/1.1.2/DOM/Traversing - jQuery JavaScript Library addのあるところにremoveもあると思うじゃないか。 removeはtraversingじゃなくてmanipulationなんだそうだ。 API/1.1.2/DOM/Manipulation - jQuery JavaScript Library
エレメントをDOMツリーに追加するのは例えばbefore。やっぱりmanipulationのところに書いてある。 addの引数に前とか後とか場所を指定する要素がないからおかしいと思った。
= PythonとJavaScriptはリテラルの表記方法がほぼ同じ。 なのでPythonの辞書をstrで文字列化して返し、
def comment_rating(request, comment_id, value):
    (中略)
    return HttpResponse(str(comment.get_rating()))
JavaScriptでevalすると…

残念、世の中そこまで甘くはなかった。

>>> x
"{'color': 'ff8866', 'valstr': '-0.78 -7/9', 'fval': -0.77777777777777779}"
>>> eval(x)
invalid label
[Break on this error] 
{'color': 'ff8866', 'valstr': '-0.78 -7/9', 'fval': -0.77777777777777779}

=
  $.getJSON(url, function(json){
    x = json;
  })
>>> x
Object color=99ff00 valstr=0.18 6/34
>>> x.valstr
"0.18 6/34"
>>> x.fval
0.17647058823529413
やっべ、激甘だったw。 少なくとも文字列がキーで、値が文字列や浮動小数点数の辞書なんかは 「Pythonの辞書→(Python組み込みの関数str)→文字列→(jQueryのgetJSON)→JavaScriptのハッシュ」 と素通しできることがわかってしまった。これは面白い。
= なんかもうAjaxでレーティングする機能がほとんどできちゃったかも。 後は今はきちんと集計しないでランダムな値を返しているので、 きちんと集計するようにすればいいだけか。
= できた。
= あうあう、 プレビューがこけるようになっているな。 レーティングを表示する部分で、プレビューの場合だけ Commentオブジェクトの代わりに辞書が来るんだった。 テストを走らせる必要があるか…。 テストを走らせるのに30分くらいかかるようになっていて困りもの。 そろそろ貧弱なノートパソコンじゃなくてラボのマシンで開発できるように整えないといけないか。
= おなかすいた。テスト終わらないな。

テスト終わった。23時15分。むう。

フィードもこける。 ユーザ情報が渡っていないから。レーティングのユーザごとの投票を見るところでこける。

4つのエラー全部がKeyError: userだからここさえ直せば直ると思うけど、 もう一回テスト掛けたら家に帰れなくなりそうだ。 諦めて明日にしよう。


= 帰りの電車で以前投票したのがどれかも表示するようにした。

Ajaxは思ったより簡単だった。


= 寝る前に日本全国ラボめぐり:グループウェアの新しい使い方「ひとり言スレ」──サイボウズ・ラボ - ITmedia Biz.IDにいくつか言いたい。

取材って難しいんだなぁ。いまいち伝わってない。

「ひとりごと」を「ひとり言」と書くのは僕は嫌いだ。

「Python温泉(開発合宿)をきっかけにまったく新しいプロジェクトを開始して開発」 とグラフに書かれるとは思わなかった(笑) 業務時間の50%を温泉に行っている疑惑がっw

ちなみにこの「まったく新しいプロジェクト」ってのがどう書くorgです。 古いプロジェクトはまだ芽が出てませんorz。

「愛用の「Happy Hacking Keybord」を購入」って聞いたら誰でもProfessional買ったと思うだろうけど、 単にデフォルトで付いていたDELLのキーボードが嫌だったのと、 個人予算での物品購入の手始めとしてとりあえずHHK Liteを買ってみたというだけだったりする。 初めて買ったものは「愛用」とは言わないよなぁ。 僕愛用しているって言ったかなぁ。 愛用しているのはThinkpadの赤いぽっちです、たぶん。

あとトラックボールは家の押し入れで3年くらい眠ってたやつ。 DELLのマウスが嫌だったので家から持ってきたのだけど、 人に貸していたmac miniが帰ってきてポインティングデバイスが必要になったので ラボ用にもう1個買ったのだった。

「京都大学在学中にインタフェース関連の研究」…してないよなぁ。 したのかなぁ。 ゲノムの4次元可視化はインターフェイス関連の研究だったのかなぁ。

「事業領域内において、すなわちWebアプリケーションなどの開発という制限があるものの」 って、僕どう書くorgが初Webアプリなんだけど、それまでの3ヶ月間は遊んでいたということ?w

「たいていのひとり言にはアドバイスが付くようになった」はいくらなんでも事実に反するというか、 そんなにアドバイスが付いたらひとりごとじゃないというか、 「おなかすいた」とかにアドバイスが付いたら嫌だ。 「アイデアレベルでアドバイスを得たい」というモチベーションで始めたわけじゃないもんなぁ、そもそも。

まぁ、まとめると、取材して原稿書く人は大変だなぁ、 100%インタビュイーの考えていることをくみ取ることは不可能なのだろうけど、 事実と違うことが書いてあるとやっぱり反論したくなるわけで。


= 昨日か一昨日に書いたけど結局貼らなかった文章に手を加えて貼る。

カンファレンスに参加することによって得られる情報や人脈などの参加者その人にとっての価値が、 参加費16000円や消費される時間などのコストを上回るなら、 参加することは合理的。

そのカンファレンスによって運営側がいくら儲けたのかは別にどうでもいい。 たくさん儲けたのなら、 それは安価な仕入れ価格で多くの人に求められる商品を作り出したということなのだから、 そのビジネス能力に対する正当な報酬なんじゃないかと。

自分も儲けたいのであれば、自分も同じような商売をすればいいだけ。 実際やってみると思ったよりも難しいかも知れないし、思ったよりも簡単かも知れない。 それはやってみなければわからない。

逆に、不当に高い値段だと憤りを感じるのなら、より安価に提供するために行動すればいい。 他人のビジネスを「不当に高い」と非難したって仕方がない。 その価格で買っている人がいて生態系として成立しているわけだから叩いても不毛な争いになるだけだ。

とここまで書いてやっぱり公開しないことにしていたのだけども、 考え直してやっぱり公開。

争いは無益だ。

有償で「不当に高い疑惑」のあるCSS Niteと、 無償で「たとえどんなにつまらない内容で来場者が退屈したとしてもクレームはお門違い」 とか言われちゃってるShibuya.JSとは、 まぁかなり対照的なカンファレンスなので、 比較して色々言うのもやり安いのだろうとは思う。 でも現実世界はデジタルではないので、 たいがいのものに「間」がある。 2点だけに注目するよりも、間も含めた複数の点を考えた方が、 より立体的に考えられると思う。

CSS Nite公式ブログ:収支概算を公表します:[Web標準の日々]:二日で16000円 、Shibuya.js:0円、 の他に、参加費無料だけど海外から人を呼んできて同時通訳付きで発表してもらったりした Python Workshop the Edge 2007(2007年6月30日開催) と 3500円のLightweight Language Spiritあたりを混ぜるといいのではないだろうか。

単純に金額だけ見ると、 shibuya.jsやPython Workshop the Edgeは 「たとえどんなにつまらない内容で来場者が退屈したとしてもクレームはお門違い」 なカンファレンスで、 CSS NiteはLL Spiritの4倍以上の濃度だと言うことになるが、どうだろうか。

Python Workshop the Edgeで発表した僕は、 「無償だから来場者を退屈させてもいい」 なんて思ったりはしなかった。 今、いくらだったのか検索してみて無償だということを初めて知った。 なので、カンファレンスが無償だったらどう、有償だったらどう、という議論には疑問を感じる。 有償のカンファレンスは来場者が退屈したら参加費を返すってわけでもないだろうし、 無償のカンファレンスであっても来場者は貴重な時間を割いてきているわけだからむげにはできないだろう。

結局のところ、何に価値を見いだすかは人によって違うわけで、 他の人間がそれをどうこう言ったって仕方がないんだよなぁ。 金額の話だって、CSS Niteに行くお金は出るけども Shibuya.jsに行くお金は出せない、って会社もあるかも知れないし、 そういうシチュエーションでは単純に金額で比較できないもんなぁ。

うん、で、結局何が言いたいんだ、僕は? 「有償v.s.無償という構図にしない」「CSS nite v.s. shibuya.jsという構図にしない」 「っていうかCSS Niteで儲かってようが儲かってなかろうが別にいいじゃん」 「高いカンファレンスに腹が立つ人たちで集まって 『安いカンファレンス』カンファレンス(実体はただの飲み会)とかやりませんか?(ぉ」

2007年07月16日

Re: 小町算

Karetta|キミならどう書く 2.0 - 2007 - その 2

想像以上にいっぱいあるなぁ。オリジナルの小町算は除算が整除じゃないからもっと正解の個数が少ないんだが…。

正解は303個。

1 123+45-67+8-9 == 100
2 123+45/6*7-8*9 == 100
3 123+4-5+67-89 == 100
4 123+4*5-6*7+8-9 == 100
5 123-45-67+89 == 100
6 123-4-5-6-78/9 == 100
7 123-4-5-6-7+8-9 == 100
8 123-4-5-6*7/8-9 == 100
9 123-4*5+6+7/8-9 == 100
10 123-4*5+6-7/8-9 == 100
11 123*4-56*7+8/9 == 100
12 123*4-56*7-8/9 == 100
13 123*4/5-6+78/9 == 100
14 123*4/5-6+7-8+9 == 100
15 123/4-5+678/9 == 100
16 123/4-5+6+78-9 == 100
17 123/4*5-67+8+9 == 100
18 12+34+5*6+7+8+9 == 100
19 12+34-5+67*8/9 == 100
20 12+34-5+6*7+8+9 == 100
21 12+34-5-6+7*8+9 == 100
22 12+34-5-6-7+8*9 == 100
23 12+34/5/6+78+9 == 100
24 12+3+4+5-6-7+89 == 100
25 12+3+4-56/7+89 == 100
26 12+3+4*5/6-7+89 == 100
27 12+3+4/5+6+7+8*9 == 100
28 12+3-4+5+67+8+9 == 100
29 12+3-4+5/67+89 == 100
30 12+3-4+5/6*7+89 == 100
31 12+3-4+5/6/7+89 == 100
32 12+3-4-5/67+89 == 100
33 12+3-4-5/6*7+89 == 100
34 12+3-4-5/6/7+89 == 100
35 12+3-4/5+6+7+8*9 == 100
36 12+3*45+6*7-89 == 100
37 12+3*4+5+6+7*8+9 == 100
38 12+3*4+5+6-7+8*9 == 100
39 12+3*4+5*6/7+8*9 == 100
40 12+3*4-5-6+78+9 == 100
41 12+3/45+6-7+89 == 100
42 12+3/4-5+6+78+9 == 100
43 12+3/4*5+6-7+89 == 100
44 12+3/4/5+6-7+89 == 100
45 12-3+4+5/6+78+9 == 100
46 12-3+4-5/6+78+9 == 100
47 12-3+4*5+6+7*8+9 == 100
48 12-3+4*5+6-7+8*9 == 100
49 12-3-4+5-6+7+89 == 100
50 12-3-4+5*6+7*8+9 == 100
51 12-3-4+5*6-7+8*9 == 100
52 12-3*4*5/6/7+89 == 100
53 12-3*4/5-6+7+89 == 100
54 12-3*4/5*6/7+89 == 100
55 12-3/45+6-7+89 == 100
56 12-3/4-5+6+78+9 == 100
57 12-3/4*5+6-7+89 == 100
58 12-3/4/5+6-7+89 == 100
59 12*34/5/6+78+9 == 100
60 12*3+456/7+8-9 == 100
61 12*3-4+5-6+78-9 == 100
62 12*3-4-5-6+7+8*9 == 100
63 12*3-4-5*6/7+8*9 == 100
64 12*3-4*5+67+8+9 == 100
65 12*3*4/5+67/8*9 == 100
66 12*3*4/5+6/7+8*9 == 100
67 12*3*4/5-6/7+8*9 == 100
68 12*3*4/5/6+7+89 == 100
69 12*3*4/5/6*7+8*9 == 100
70 12/3+4*5-6-7+89 == 100
71 12/3+4*5*6-7-8-9 == 100
72 12/3+4*5*6*7/8-9 == 100
73 12/3+4/56+7+89 == 100
74 12/3+4/5*6+7+89 == 100
75 12/3+4/5/6+7+89 == 100
76 12/3-4/56+7+89 == 100
77 12/3-4/5*6+7+89 == 100
78 12/3-4/5/6+7+89 == 100
79 12/3*4-5+6/7+89 == 100
80 12/3*4-5-6/7+89 == 100
81 12/3*4*5/6+78+9 == 100
82 12/3*4/5*6-7+89 == 100
83 12/3/4+5*6+78-9 == 100
84 1+234-56-7-8*9 == 100
85 1+234*5*6/78+9 == 100
86 1+234*5/6-7-89 == 100
87 1+234*5/6/7+8*9 == 100
88 1+234/5+6+7*8-9 == 100
89 1+23+45/6+78-9 == 100
90 1+23+4+5+67+8/9 == 100
91 1+23+4+5+67-8/9 == 100
92 1+23+4+5/67+8*9 == 100
93 1+23+4+5/6*7+8*9 == 100
94 1+23+4+5/6/7+8*9 == 100
95 1+23+4-5/67+8*9 == 100
96 1+23+4-5/6*7+8*9 == 100
97 1+23+4-5/6/7+8*9 == 100
98 1+23+4/5-6-7+89 == 100
99 1+23-4+56+7+8+9 == 100
100 1+23-4+56/7+8*9 == 100
101 1+23-4+5+678/9 == 100
102 1+23-4+5+6+78-9 == 100
103 1+23-4-5+6+7+8*9 == 100
104 1+23-4*5/6+7+8*9 == 100
105 1+23-4/5-6-7+89 == 100
106 1+23*4+567/8/9 == 100
107 1+23*4+56/7+8-9 == 100
108 1+23*4+56/7*8/9 == 100
109 1+23*4+5-6+78/9 == 100
110 1+23*4+5-6+7-8+9 == 100
111 1+23*4+5/6+7+8/9 == 100
112 1+23*4+5/6+7-8/9 == 100
113 1+23*4-5+6+7+8-9 == 100
114 1+23*4-5+6+7*8/9 == 100
115 1+23*4-5/6+7+8/9 == 100
116 1+23*4-5/6+7-8/9 == 100
117 1+23*4*5/6/7+89 == 100
118 1+23*4/5-6+78+9 == 100
119 1+23*4/5*6+7/8-9 == 100
120 1+23*4/5*6-7/8-9 == 100
121 1+23*4/5/6+7+89 == 100
122 1+23/4+5+6/7+89 == 100
123 1+23/4+5-6/7+89 == 100
124 1+2+345/6/7+89 == 100
125 1+2+34+56+7+8/9 == 100
126 1+2+34+56+7-8/9 == 100
127 1+2+34-5+67-8+9 == 100
128 1+2+34*5+6-7-8*9 == 100
129 1+2+34*5/6+78-9 == 100
130 1+2+34/5/6+7+89 == 100
131 1+2+3+45/6+78+9 == 100
132 1+2+3+4+5+6+7+8*9 == 100
133 1+2+3-45+67+8*9 == 100
134 1+2+3-4+5+6+78+9 == 100
135 1+2+3-4*5+6*7+8*9 == 100
136 1+2+3*4-5-6+7+89 == 100
137 1+2+3*4-5*6/7+89 == 100
138 1+2+3*4*56/7-8+9 == 100
139 1+2+3*4*5+6*7*8/9 == 100
140 1+2+3*4*5/6+78+9 == 100
141 1+2+3/4+56/7+89 == 100
142 1+2+3/4-5+6+7+89 == 100
143 1+2-3+4+5/6+7+89 == 100
144 1+2-3+4-5/6+7+89 == 100
145 1+2-3*4+5*6+7+8*9 == 100
146 1+2-3*4-5+6*7+8*9 == 100
147 1+2-3/4+56/7+89 == 100
148 1+2-3/4-5+6+7+89 == 100
149 1+2*345/67+89 == 100
150 1+2*34-56+78+9 == 100
151 1+2*34/5*67/8-9 == 100
152 1+2*3+4+5+67+8+9 == 100
153 1+2*3+4+5/67+89 == 100
154 1+2*3+4+5/6*7+89 == 100
155 1+2*3+4+5/6/7+89 == 100
156 1+2*3+4-5/67+89 == 100
157 1+2*3+4-5/6*7+89 == 100
158 1+2*3+4-5/6/7+89 == 100
159 1+2*3+4*5-6+7+8*9 == 100
160 1+2*3+4*5/6*7+8*9 == 100
161 1+2*3+4/5+6+78+9 == 100
162 1+2*3-4+56/7+89 == 100
163 1+2*3-4-5+6+7+89 == 100
164 1+2*3-4*5/6+7+89 == 100
165 1+2*3-4/5+6+78+9 == 100
166 1+2*3*4*5/6+7+8*9 == 100
167 1+2*3/4+5+6+78+9 == 100
168 1+2*3/4*5*6+78-9 == 100
169 1+2/34+5*6+78-9 == 100
170 1+2/3+4+5-6+7+89 == 100
171 1+2/3+4+5*6+7*8+9 == 100
172 1+2/3+4+5*6-7+8*9 == 100
173 1+2/3+4*5/6+7+89 == 100
174 1+2/3-4+56+7*8-9 == 100
175 1+2/3*4+5*6+78-9 == 100
176 1+2/3/4+5*6+78-9 == 100
177 1-23+4*5+6+7+89 == 100
178 1-23-4+5*6+7+89 == 100
179 1-23-4-5+6*7+89 == 100
180 1-2+34-5+67/8*9 == 100
181 1-2+34-5+6/7+8*9 == 100
182 1-2+34-5-6/7+8*9 == 100
183 1-2+34/5*6+7*8+9 == 100
184 1-2+34/5*6-7+8*9 == 100
185 1-2+3+45+6+7*8-9 == 100
186 1-2+3+4+5+6/7+89 == 100
187 1-2+3+4+5-6/7+89 == 100
188 1-2+3*45*67/89 == 100
189 1-2+3*45/6+7+8*9 == 100
190 1-2+3*4+5+67+8+9 == 100
191 1-2+3*4+5/67+89 == 100
192 1-2+3*4+5/6*7+89 == 100
193 1-2+3*4+5/6/7+89 == 100
194 1-2+3*4-5/67+89 == 100
195 1-2+3*4-5/6*7+89 == 100
196 1-2+3*4-5/6/7+89 == 100
197 1-2+3*4*5+6*7+8-9 == 100
198 1-2+3*4*5-6+7*8-9 == 100
199 1-2+3*4/5*6*7+8+9 == 100
200 1-2-34+56+7+8*9 == 100
201 1-2-3+45+67*8/9 == 100
202 1-2-3+45+6*7+8+9 == 100
203 1-2-3+45-6+7*8+9 == 100
204 1-2-3+45-6-7+8*9 == 100
205 1-2-3+4*56/7+8*9 == 100
206 1-2-3+4*5+67+8+9 == 100
207 1-2*3+4*5+6+7+8*9 == 100
208 1-2*3-4+5*6+7+8*9 == 100
209 1-2*3-4-5+6*7+8*9 == 100
210 1-2/34+5*6+78-9 == 100
211 1-2/3+4+5-6+7+89 == 100
212 1-2/3+4+5*6+7*8+9 == 100
213 1-2/3+4+5*6-7+8*9 == 100
214 1-2/3+4*5/6+7+89 == 100
215 1-2/3-4+56+7*8-9 == 100
216 1-2/3*4+5*6+78-9 == 100
217 1-2/3/4+5*6+78-9 == 100
218 1*234+5-67-8*9 == 100
219 1*234/56+7+89 == 100
220 1*234/56*7+8*9 == 100
221 1*23+4+56/7*8+9 == 100
222 1*23+4+5+67-8+9 == 100
223 1*23-4+5-6-7+89 == 100
224 1*23-4-56/7+89 == 100
225 1*23*4+56/7+8/9 == 100
226 1*23*4+56/7-8/9 == 100
227 1*23*4+5-6+7/8+9 == 100
228 1*23*4+5-6-7/8+9 == 100
229 1*23*4+5/6+78/9 == 100
230 1*23*4+5/6+7-8+9 == 100
231 1*23*4-56/7/8+9 == 100
232 1*23*4-5+6+7+8/9 == 100
233 1*23*4-5+6+7-8/9 == 100
234 1*23*4-5/6+78/9 == 100
235 1*23*4-5/6+7-8+9 == 100
236 1*23*4*5/6+7+8+9 == 100
237 1*23*4/5*6-78/9 == 100
238 1*23*4/5*6-7+8-9 == 100
239 1*23/4+5-6+7+89 == 100
240 1*23/4+5*6+7*8+9 == 100
241 1*23/4+5*6-7+8*9 == 100
242 1*23/4*5+678/9 == 100
243 1*23/4*5+6+78-9 == 100
244 1*23/4*5/6+7+89 == 100
245 1*23/4*5/6*7+8*9 == 100
246 1*2+34+56+78/9 == 100
247 1*2+34+56+7-8+9 == 100
248 1*2+34+5+67*8/9 == 100
249 1*2+34+5+6*7+8+9 == 100
250 1*2+34+5-6+7*8+9 == 100
251 1*2+34+5-6-7+8*9 == 100
252 1*2+34-56/7+8*9 == 100
253 1*2+34*5+6/7-8*9 == 100
254 1*2+34*5-67/8*9 == 100
255 1*2+34*5-6/7-8*9 == 100
256 1*2+3+45+67-8-9 == 100
257 1*2+3+4*5+678/9 == 100
258 1*2+3+4*5+6+78-9 == 100
259 1*2+3-4+5*6+78-9 == 100
260 1*2+3*45-6*7*8/9 == 100
261 1*2+3*45*6/7-8-9 == 100
262 1*2+3*4+5-6+78+9 == 100
263 1*2+3/4+5+6+78+9 == 100
264 1*2-3+4+56/7+89 == 100
265 1*2-3+4-5+6+7+89 == 100
266 1*2-3+4*5-6+78+9 == 100
267 1*2-3/4+5+6+78+9 == 100
268 1*2*34+56-7-8-9 == 100
269 1*2*34+5*67/8-9 == 100
270 1*2*34-5+6*7*8/9 == 100
271 1*2*34/5*6/7+89 == 100
272 1*2*3+45/6+78+9 == 100
273 1*2*3+4+5+6+7+8*9 == 100
274 1*2*3-45+67+8*9 == 100
275 1*2*3-4+5+6+78+9 == 100
276 1*2*3-4*5+6*7+8*9 == 100
277 1*2*3*4+5+6+7*8+9 == 100
278 1*2*3*4+5+6-7+8*9 == 100
279 1*2*3*4+5*6/7+8*9 == 100
280 1*2*3*4-5-6+78+9 == 100
281 1*2*3/4+5*6+78-9 == 100
282 1*2/3+4+5/6+7+89 == 100
283 1*2/3+4-5/6+7+89 == 100
284 1/23+4+5/6+7+89 == 100
285 1/23+4-5/6+7+89 == 100
286 1/2+34+56-7+8+9 == 100
287 1/2+34-5+6+7*8+9 == 100
288 1/2+34-5+6-7+8*9 == 100
289 1/2+34-5*6+7+89 == 100
290 1/2+3+45+6*78/9 == 100
291 1/2+3+4+5+6-7+89 == 100
292 1/2+3+4+5*6/7+89 == 100
293 1/2+3*4-5+6+78+9 == 100
294 1/2-34-5+67+8*9 == 100
295 1/2-3+45/6+7+89 == 100
296 1/2-3+4+5*6+78-9 == 100
297 1/2-3-4+5+6+7+89 == 100
298 1/2-3*4+5*6-7+89 == 100
299 1/2-3*4/5+6+7+89 == 100
300 1/2*3+4+5/6+7+89 == 100
301 1/2*3+4-5/6+7+89 == 100
302 1/2/3+4+5/6+7+89 == 100
303 1/2/3+4-5/6+7+89 == 100
OPS = ["", "+", "-", "*", "/"]
EQ = "1_2_3_4_5_6_7_8_9 == 100"
count = 0

def dig(eq = EQ, level = 8):
    global count
    if level:
        for op in OPS:
            dig(eq.replace("_", op, 1), level - 1)

    elif eval(eq):
        count += 1
        print count, eq

dig()
ちなみに、最初に5**8を計算して、40万程度なので、高速化するより素直に書いた方が速いと判断、 書いて実行して70秒程度で解決。

しかし、まだトラックバックがないように見えたので解いてみたのだけども、トラックバックが表示されないみたいだなぁ。なんでだろう。

ググってみたら2個見つかった。そしてPythonの解答がすでにあることがわかったので2番目の問題を解く気が失せてしまった。see odz buffer


= とか言いながら作ってしまった。
from operator import add, mul, sub, div

def join(a, b):
    return a * 10 + b

OPS = {
    '+': add,
    '-': sub,
    '*': mul,
    '/': div,
    '' : join
}

EQ = "1_2_3_4_5_6_7_8_9 == 100"
count = 0

def dig(eq = "1", value = 1, next = 2):
    global count
    if next < 10:
        for opname, op in OPS.iteritems():
            dig(
                eq + opname + str(next),
                op(value, next),
                next + 1)

    elif value == 100:
        count += 1
        print count, eq

dig()

= 一般化バージョンまで作ってしまった。 下のコードのOPSって辞書が「表示、関数、実行順序」の3つの値のタプルになっていて、 ここを書き換えることで両方の問題を解ける。

下のコードは問題の前半を解くコードで、実行すると303個の解が表示される。一方、

from operator import add, mul, sub, div

def join(a, b):
    return a * 10 + b

# op_name, op_function, op_order
OPS = [
    ("", join, 0),
    ("+", add, 2),
    ("-", sub, 2),
    ("*", mul, 1),
    ("/", div, 1)]

EQ = "1_2_3_4_5_6_7_8_9 == 100"
count = 0

def dig(ops = [], level = 8):
    global count
    if level:
        for op in OPS:
            dig(ops + [op], level - 1)

    else:
        eq, result = evaluate(ops)
        if result == 100:
            count += 1
            print count, eq

def evaluate(ops):
    values = [None] + range(1, 10) + [None]
    
    eqstr = "1"
    i = 1
    for i, op in enumerate(ops):
        eqstr += op[0] + str(i + 2)

    priority = 0
    while priority < 3:
        for i, op in enumerate(ops):
            if op[2] == priority:
                values = values[:i + 1] +\
                    [op[1](values[i + 1], values[i + 2])] +\
                    values[i + 3:]
                del ops[i]
                break
        else:
            priority += 1

    return eqstr, values[1]

dig()
OPSを下のように書き換えると問題の後半の解が出せる。
OPS = [
    ("", join, 0),
    ("+", add, 1),
    ("-", sub, 1),
    ("*", mul, 1),
    ("/", div, 1)]
答えは156個。あれ、さっきのは144個だったけど…? 理由は簡単で、解が144個になるバージョンは、 joinの優先順位が他の演算子よりも高いことを見落としているから、
1/2/3/4+5+67-8-9
これの計算結果が100になる。 手計算してみるとわかるけど、これは55になる。 でもjoinの優先順位を高く設定していないと 5+67が(5+6)7になり、117-8-9で100になるわけだ。
= そしてこの「一般化バージョン」のいいところは evaluateの1行目を下のように書き換えるだけで 整除じゃないバージョンにできることだ。
    values = [None] + [float(x) for x in range(1, 10)] + [None]
本当は浮動小数点演算の誤差のからみがあるので、 有理数オブジェクトと有理数用の加減乗除の関数を用意する必要があるけど、 面倒なので省略。 下のような結果が出るようになる。
100 1/2*3/4*56+7+8*9
101 1/2/3*456+7+8+9
整除でないバージョンは前半が101個、後半が67個になるかな。 誤差が原因で多少変動するかも。でももうめんどくさくなった。

一応貼っておく。

1 123+45-67+8-9
2 123+4-5+67-89
3 123+4*5-6*7+8-9
4 123-45-67+89
5 123-4-5-6-7+8-9
6 12+34+5*6+7+8+9
7 12+34-5+6*7+8+9
8 12+34-5-6+7*8+9
9 12+34-5-6-7+8*9
10 12+3+4+5-6-7+89
11 12+3+4-56/7+89
12 12+3-4+5+67+8+9
13 12+3*45+6*7-89
14 12+3*4+5+6+7*8+9
15 12+3*4+5+6-7+8*9
16 12+3*4-5-6+78+9
17 12-3+4*5+6+7*8+9
18 12-3+4*5+6-7+8*9
19 12-3-4+5-6+7+89
20 12-3-4+5*6+7*8+9
21 12-3-4+5*6-7+8*9
22 12*3-4+5-6+78-9
23 12*3-4-5-6+7+8*9
24 12*3-4*5+67+8+9
25 12/3+4*5-6-7+89
26 12/3+4*5*6-7-8-9
27 12/3+4*5*6*7/8-9
28 12/3/4+5*6+78-9
29 1+234-56-7-8*9
30 1+234*5*6/78+9
31 1+234*5/6-7-89
32 1+23-4+56+7+8+9
33 1+23-4+56/7+8*9
34 1+23-4+5+6+78-9
35 1+23-4-5+6+7+8*9
36 1+23*4+56/7+8-9
37 1+23*4+5-6+7-8+9
38 1+23*4-5+6+7+8-9
39 1+2+34-5+67-8+9
40 1+2+34*5+6-7-8*9
41 1+2+3+4+5+6+7+8*9
42 1+2+3-45+67+8*9
43 1+2+3-4+5+6+78+9
44 1+2+3-4*5+6*7+8*9
45 1+2+3*4-5-6+7+89
46 1+2+3*4*56/7-8+9
47 1+2+3*4*5/6+78+9
48 1+2-3*4+5*6+7+8*9
49 1+2-3*4-5+6*7+8*9
50 1+2*34-56+78+9
51 1+2*3+4+5+67+8+9
52 1+2*3+4*5-6+7+8*9
53 1+2*3-4+56/7+89
54 1+2*3-4-5+6+7+89
55 1+2*3*4*5/6+7+8*9
56 1-23+4*5+6+7+89
57 1-23-4+5*6+7+89
58 1-23-4-5+6*7+89
59 1-2+3+45+6+7*8-9
60 1-2+3*4+5+67+8+9
61 1-2+3*4*5+6*7+8-9
62 1-2+3*4*5-6+7*8-9
63 1-2-34+56+7+8*9
64 1-2-3+45+6*7+8+9
65 1-2-3+45-6+7*8+9
66 1-2-3+45-6-7+8*9
67 1-2-3+4*56/7+8*9
68 1-2-3+4*5+67+8+9
69 1-2*3+4*5+6+7+8*9
70 1-2*3-4+5*6+7+8*9
71 1-2*3-4-5+6*7+8*9
72 1*234+5-67-8*9
73 1*23+4+56/7*8+9
74 1*23+4+5+67-8+9
75 1*23-4+5-6-7+89
76 1*23-4-56/7+89
77 1*23*4-56/7/8+9
78 1*2+34+56+7-8+9
79 1*2+34+5+6*7+8+9
80 1*2+34+5-6+7*8+9
81 1*2+34+5-6-7+8*9
82 1*2+34-56/7+8*9
83 1*2+3+45+67-8-9
84 1*2+3+4*5+6+78-9
85 1*2+3-4+5*6+78-9
86 1*2+3*4+5-6+78+9
87 1*2-3+4+56/7+89
88 1*2-3+4-5+6+7+89
89 1*2-3+4*5-6+78+9
90 1*2*34+56-7-8-9
91 1*2*3+4+5+6+7+8*9
92 1*2*3-45+67+8*9
93 1*2*3-4+5+6+78+9
94 1*2*3-4*5+6*7+8*9
95 1*2*3*4+5+6+7*8+9
96 1*2*3*4+5+6-7+8*9
97 1*2*3*4-5-6+78+9
98 1*2/3+4*5/6+7+89
99 1/2*34-5+6-7+89
100 1/2*3/4*56+7+8*9
101 1/2/3*456+7+8+9
1 123+45-67+8-9
2 123+4-5+67-89
3 123-45-67+89
4 123-45/6+78+9
5 123-4-5-6-7+8-9
6 12+3+4+5-6-7+89
7 12+3+4+5/6+7+89
8 12+3-4+5+67+8+9
9 12+3*4-56+7+89
10 12+3*4/5+6-7+89
11 12-3+4+5*6-7+8-9
12 12-3-4+5-6+7+89
13 12-3-4*5+6+78-9
14 12-3*4+56+7-8+9
15 12*3-4+5-6+78-9
16 12/3+4-5*6-7+89
17 12/3*4+5+6*7-89
18 1+23+4+5-6*7-89
19 1+23-4+56+7+8+9
20 1+23-4+5+6+78-9
21 1+23-4-5*6-7+8+9
22 1+23-4*5+6-7-8+9
23 1+23-4*5-6+7+8-9
24 1+2+34-5+67-8+9
25 1+2+3+4+5*6-7+8+9
26 1+2+3+4*5+67-8-9
27 1+2+3-4+5+6+78+9
28 1+2+3-4+5/6*78+9
29 1+2+3-4*5-6+7+89
30 1+2+3*4-5-6+78+9
31 1+2*3+4+5*6-7+8-9
32 1+2*3-4+5-6+7+89
33 1+2*3-4*5+6+78-9
34 1+2*3*4+56+7-8+9
35 1+2/3+4+5-6+7+89
36 1+2/3+4*5+6+78-9
37 1-2+3-4+5*6-7+89
38 1-2+3*4-5*6-7+89
39 1-2*3-4+5+6+7+89
40 1-2/3+4*5*6+7-8-9
41 1-2/3-4+5*6+7+89
42 1*23+4+5+67-8+9
43 1*23-4+5-6-7+89
44 1*23-4+5/6+7+89
45 1*2+34+56+7-8+9
46 1*2+3+45+67-8-9
47 1*2+3*4+56+7+8+9
48 1*2+3*4+5+6+78-9
49 1*2+3*4-5*6-7+8+9
50 1*2+3*4*5+6-7-8+9
51 1*2+3*4*5-6+7+8-9
52 1*2-3+4-5+6+7+89
53 1*2-3+4*5*6-7+8+9
54 1*2*34+56-7-8-9
55 1*2*3+4+5*6-7+8+9
56 1*2*3+4*5+67-8-9
57 1*2*3-4+5+6+78+9
58 1*2*3-4+5/6*78+9
59 1*2*3-4*5-6+7+89
60 1*2*3*4-5-6+78+9
61 1*2/3/4+5*6+78-9
62 1/2+3-4+5*6*7-89
63 1/2+3*4+5-6+78+9
64 1/2+3*4*5+6+7+8+9
65 1/2-3*4/5+6+7+89
66 1/2*34-5+6-7+89
67 1/2/3*456+7+8+9

odz buffer - 小町算 #3。 まじめに有理数で計算すると後半の問題の答えが68個だって。僕のより1個多い。 差が出るのはこれ「1*2/3/4-5+6*78+9」。 浮動小数点数方式では100.00000000000003になってしまう。

はいしゃ日記

歯医者。
= 320GBのHDDが10800円で売っていたので喜んで買ってきたのだけど、 二つ買えばよかったかもと。 あちこちのHDDに分散していたかさばるメディアファイルを集めてきたら、 もう70%くらい埋まってしまった。

今のノートパソコンは32GBしかHDDがない。 最初に換装しておけばよかったんだけどな。 なのでHDD丸ごとSubversionのリポジトリに入れるという荒技を考えたのだけど、 Subversionで管理されているフォルダの中に別のリポジトリからチェックアウトしたら、 なんかうまく行かなさそうな気がする。どうだろう、そこのところ。 バージョン管理していないフォルダを作ってその中にチェックアウトすればいいのだろうけど、 やっぱり一発でバックアップできて欲しいなぁ。


= あれ、気がついたらもう25時だ。

今日は昼前に起きて、攻殻機動隊を見て、14時頃に 「攻殻機動隊は夜でも見られるから今買い物に行くべき」 と気づいて東急ハンズへ。 色々買ってきた。 カッティングマシーン欲しい。

うどんやの生姜飴を3袋買ってきた。一袋に40個以上入ってた。


= あーあ。 月曜日は普通に朝起きて普通に活動してJython本の原稿を進めようと思っていたのに、 朝7時に寒くて起きて(窓を開けてTシャツで寝ていたせい)、 シャワーでも浴びればいいものを重ね着してもう一度寝てしまい、 起きたら昼過ぎ。

秋葉原の喫茶ルノワールで執筆しようと思っていたけど、微妙。

昨日東急ハンズで買ってきた千円くらいの魚眼レンズを試し撮り。

P1070844
P1070844 posted by (C)にしお

鏡がお風呂にしかないのでそこで撮ったんだけど、だいぶ光量不足。

ちなみにすごく大きい(高価な)レンズに見えるかも知れないけども、ドアスコープです。 「マクロ撮影モードでズームをテレスコープ(望遠)端にして、ぎりぎりまで近寄って撮る」という なんか矛盾した行為の結果が上の写真。 ドアスコープと対物レンズの間をもっと近づけられたらテレ端にする必要がなくなって もっと視野が広がるのかも知れない。 だけど金属の筒に入っているので僕の手持ちの工具では切断できない。 金属用の糸鋸で切れるかなぁ? 中のレンズを割らないように切るのは難しいかなぁ。 分解の簡単なドアスコープがないか探してみることにしよう。

いま90度くらいしかないなぁ。 これじゃぁあんまり面白くないなぁ。180度くらいの魚眼レンズを作りたいなぁ。


= あ、今朝地震があったような気がしたのは本物だったのか。 以前(西尾泰和のブログ: 地震上野レオナルド日記)、 「遠くで大地震があったのに違いない」と思ったのが勘違いだったので、 今度も似たような感じだと思ったら新潟が大変なことになっていた。
= あと炊飯器の中が大変なことになってた。 きっとアルコールできてた。かもされたぞ。

処分したのは昨日か一昨日なんだけど、 処分の方法が悪かったと見えて二次災害が進行中。 キッチンの流し周辺に「どろそふぃら・めらのがすたー」が大発生。

参考文献:ショウジョウバエ - Wikipedia

アルコールの大好きな奴らですが、いったいどこからかぎつけてやってきたのやら。 まぁ、害はないので単にうっとうしいだけですが。


= 結局、もう21時か。執筆が進んでない。ダメだなぁ、なにやってんだろう。

次のLTのためにFlashを勉強しようかと思ったり。 FlashならPowerPointに貼れたはずだ…と。

Flashの勉強はまずはお絵かきアプリ風のものから始めようかと思っていたけども、 それをやると未踏ユースの別のプロジェクトとコンフリクトしそうなので後回しに。

はやくJython本の執筆を終わらせて、 「終わらせたら買おう」って思ってたものを買いたい。 MIDI音源とマインドストームNXTとTシャツくん。

MIDI音源はウダーの練習のために。 今は年賀状用のプリントごっこでやっているTシャツ作成だけど、 Tシャツくんを使えば毎回ランプを取り替えたりしなくていいということがわかった。 プリントゴッコはマグネシウムランプで焼き切るタイプで、Tシャツ君は紫外線で 水溶性に変化する素材を使うタイプのようだ。

しかしお馬鹿なことに、プリントゴッコのスクリーンがまだ1枚余っているのに、 新しい5枚入りのを買ってきてしまった。

まぁいいか。

魚眼レンズの件は、目視の場合180度近い画角があるので、 試しにカメラを可能な限りワイド端でつかってみたら画角が広くなった。 そのかわり、画面中の像のある範囲は狭くなる。 あと黒い範囲が多いせいでプレビューと撮影後の写真の明るさの違いが激しい。 まぁ、でも面白い写真が撮れそうだ。次の週末晴れたら隅田川に行こう。

2007年07月13日

なんか日記

「疲れた」と書くと目からフィードバックして増幅されるので書いてはいけない。 疲れてない!

そろそろJython本を仕上げないとヤバイらしい。


= 朝ご飯:爽快ビタミン+昼ご飯:鰻丼弁当+おやつ:ネオビタミン&人参エキス +晩ご飯:とろろオクラそば+夜食:ネオビタミン

無理矢理栄養補給。 でもさすがに体はだいぶ楽になった。

精神的ドーピング:2倍速再生


= 駅の吊り広告でハリーポッターの宣伝をしていて、 何気なく見たらハーマイオニーの髪が黒くなっていてびびった。

よく見たらハーマイオニーじゃない気もしてきた。

ハーマイオニーは死んだのかな…。


= 吊り広告。

「変身忍者嵐」

どう見ても忍者に見えない。

「忍法変化の術が覚醒する。時代劇風仮面ライダー、今ここに復活!」

なるほど。


= doukakuでは、コメントの本文をbody、 それから適当な処理をしたものをbody_htmlとし、 body_htmlを使ってレンダリングをしている。 今のところまだマークアップとかは入れていないので、 不等号などをエスケープしてpreタグで囲むだけ。

で、一覧ページでは__str__の表示をしているが、 bodyを生で使っているのでタグが通ってしまうことが指摘された。

とりあえず入り口でbodyもエスケープすることにしてみた &今までのエスケープされていないものもきちんと表示するために、 表示部分にもエスケープを入れた。

よく考えたらいわゆる「あってはいけないものを取り除く処理(サニタイズ)」と違って、 エスケープは2回掛けると二重にかかってしまう。 これじゃダメだ。

面倒がらずに古いデータの変更をする必要があったんだな。。

この手の問題に対する王道はなんなんだろう。 よく考えてみるとユーザが任意の文字列を挿入できるフィールドに JavaScriptとかで変なものを仕込めば、 管理者が管理画面でそれを見たときに発動して破壊行為をするようなことも可能か。 管理画面で表示されうる文字列はもれなく無害化しておく必要があるのかな。 もしくは__str__などはもれなく無害化してから出力するようにしておくか。


= GoogleAnalyticsの結果を見ると面白い。 アメリカ国内だとカリフォルニア州がだんとつ。 [あとでスクリーンショット貼る]
= なんかこんなバッチファイルを書いた。
perl -e %1
php -r %1
ruby -e %1
python -c %1
で、こうやって使う。一番上の行だけが自分で入力したもの。
C:\>for4.bat "print (0 or 1);"

C:\>perl -e "print (0 or 1);"
1
C:\>php -r "print (0 or 1);"
1
C:\>ruby -e "print (0 or 1);"
0
C:\>python -c "print (0 or 1);"
1
で、なんでRubyだけ結果が0なのか、それが理解できない。

Rubyは0が真なんだそうな。

ちなみにこうやるとPythonだけ結果が違う。

C:\>for4.bat "print 0 or 1;"

C:\>perl -e "print 0 or 1;"
0
C:\>php -r "print 0 or 1;"
0
C:\>ruby -e "print 0 or 1;"
0
C:\>python -c "print 0 or 1;"
1
これは他の言語が「(print 0) or 1」なのに対し、Pythonは「print (0 or 1)」だから。

最後のセミコロンを削るとPHPだけパースエラーになる。

だれかPerlだけ挙動が違う例を教えてください(笑)


= Djangoのフィード配信システムを使ってフィードを作っているはずだけど、 IEではダウンロードになってしまうというフィードバックが。 うーん。 何がいけないんだろう。
= 7月6日に露木さんと常山さんに教えてもらった、 スクリプトからDjangoのデータベースAPIとか使う方法について書かれた Using Django Models In Batch Jobs : Django Aware を参考に、こんなスクリプトを書いてみた。

doukakuの言語(Lang)モデルはcountっていうフィールドを持っていて、 ユーザがその言語を選んで投稿するたびに1ずつ増えるのだけども、 たまに不整合になることがある。 原因は僕が新しい言語を追加して管理者としていじるからで、 いじったときも値が更新されるようにすればいいんだけどもそれはまだやってない。

下のスクリプトは「値が不整合になっているものを見つけて修正する」っていうスクリプト。

import os
import sys
 
os.environ['DJANGO_SETTINGS_MODULE'] =  'doukaku_proj.settings'
sys.path.append(os.path.abspath('/home/nishio'))
sys.path.append(os.path.abspath(r"C:\django"))

from doukaku import models

for lang in models.Lang.objects.all():
    c = models.Comment.objects.filter(lang=lang).count()
    if c != lang.count:
      lang.count = c
      print lang.slug, c
      lang.save()

print "ok."
for文以降をmanage shellからexecfileして使っていたのだけど、 これで実行属性をつけておけば普通に実行するだけで修正ができる。 楽ちん。 これは電車の中で書いているのでサーバでも動くかどうかはわかんない。 Windowsでは動いた。
= ところで、doukakuを作る上で僕はまだSQLを一文字も書いたことがない。 今日、テーブルにカラムを追加する時にはALTARを使うといいと聞いたので 「ついにSQLデビューか?!」と意気込んだのだけど、 SQLite Database Browserのメニューに「Edit Table」ってのがあったのでそれでやった。 SQLデビューはいつになることやら。

ウェブアプリを作るというと、 まずMySQLをインストールしてCREATE TABLEとかから教え始める説明が多いけども、 アイデアを形にする上でそういう知識が必須ではない時代になったのだなぁ。 ちょうどmallocとか知らなくてもPythonで可変長のリストを使ってコーディングができるのと同じように。


= Djangoのテンプレートは テンプレートでの出力は、デフォルトでエスケープして、 オプションでエスケープを外せる方がいいと思うけども、 Djangoがそれをやらないのは「HTMLに特化しない」 ということになっているからだろうか。
= 個人的に、ソースコードは行数二桁に保ちたくて、 100行を超えるとイエローゾーン、200行になるとかなりダメで、 300行を超えると完全にレッドゾーン、400行で「針が振り切れている」だと思う。

doukakuのコードはそろそろmodels.pyやviews.pyの針が振り切れそうなので リファクタリングを加えよう。


= 上で書いたUsing Django Models In Batch Jobs : Django Aware を参考にしたスクリプトはサーバでもちゃんと動いた。

2007年07月09日

未踏ユースのブースト会議とかDjangoとか日記

未踏ユースの2006年度採用者のリストはどこにあるんだ…。 サイドバーに出ていないのはつけ忘れだろうか?
= 未踏ユースのブースト会議も回を重ねて、 OBがどんどん増えてきた。 かつてOBがまだ少なかった頃は (自分の発表で精一杯の現役の代わりに) 色々質問したりコメントしたりすることも僕たちOBの役目であったけども、 こんなにOBが増えたのならあんまり「頑張って質問する」必要はないのではないだろうか。

もちろん質問を控えたりはしなくていいと思うけども、 ブースト会議の「時間」というリソースは限られている、 ということを意識しておく必要はあるだろう。

あまりネガティブなことを言わないように気をつけよう。 僕にとってメリットの見えないものや、 失敗する可能性の高く見えるものであっても、 それを指摘して萎えさせてはいけない。 失敗しない安牌プロジェクトからはイノベーションは生まれない。

こういう「場」をもっと増やすことを考えた方がいいのかも知れないなぁ。 限られたリソースだから節約する、じゃなくて、 リソースが少ないからもっと増やす、という方針。 「ブースト会議の後サイボウズラボでextended boost」っての今回で2回目だったけど、 恒例にしてしまうのもありかもなぁ。 前回は人が多かったので追加発表とかが多かったけど、 今回は人が少なかったのでぷち開発合宿(2時間だけ)だった。

あああっ、 成果報告会とLL魂とがかぶってるっ。 成果報告会参加したい…orz。


=

LinkChecker - check websites and HTML documents for broken links


= タグや言語の「デフォルトの順番」を決めたい。
class Lang(models.Model):
    (中略)
    class Meta:
        ordering = ["caption"]
captionフィールドの値で並べた。 複数個でソートしたいケースがあるのでリストだと言うことに注意。
= get_absolute_urlは必ずスラッシュで終わるようにしておくと、 無駄な(チェックの)労力が省ける。
= LinkChecker - check websites and HTML documents for broken links を使ってリンク切れのテストをする話の続き。

インストールするとlinkcheckerってのがPythonのScriptsフォルダに入る。 ここにパスを通しているのならコマンドラインで「linkchecker http://localhost:8000/」 とかできるようになる。でもこれをやると全部のリンクを本当にたどろうとするので大変。 「-r 1」をつけて、指定したURLから1回だけリンクをたどるのがいいのではないだろうか。

C:\django\doukaku_proj>linkchecker http://localhost:8000/4/ -r 1
LinkChecker 4.7              Copyright (C) 2000-2006 Bastian Kleineidam
LinkChecker comes with ABSOLUTELY NO WARRANTY!
This is free software, and you are welcome to redistribute it
under certain conditions. Look at the file `LICENSE' within this
distribution.
Get the newest version at http://linkchecker.sourceforge.net/
Write comments and bugs to calvin@users.sourceforge.net

Start checking at 2007-07-09 14:44:09+009

URL        `nested'
Name       `ネスト表示'
Parent URL http://localhost:8000/4/, line 45, col 1
Real URL   http://localhost:8000/4/nested/
Check Time 7.297 seconds
Info       Redirected to http://localhost:8000/4/nested/.
Warning    HTTP 301 (moved permanent) encountered: you should
           update this link.
Result     Valid: 200 OK

URL        `flatten'
Name       `フラット表示'
Parent URL http://localhost:8000/4/, line 46, col 1
Real URL   http://localhost:8000/4/flatten/
Check Time 7.266 seconds
Info       Redirected to http://localhost:8000/4/flatten/.
Warning    HTTP 301 (moved permanent) encountered: you should
           update this link.
Result     Valid: 200 OK

That's it. 23 links checked. 2 warnings found. 0 errors found.
Stopped checking at 2007-07-09 14:44:21+009 (12 seconds)
2 warnings、0 errors。 このwarningは「href="flatten/"」とすべきところが「href="flatten"」であったことが原因で リダイレクトが起きたことに関するもの。 linkchecker -r 1 -q -F html http://localhost:8000/4/ とかやってHTML形式で出力してもいいかもしれない。 その他のオプションについては-hで表示される。
= テストにこんなの書いちゃった。まぁ、成功か失敗かの判断は目視。出力に404が含まれてたら失敗、ってのでもいいのかも。
    def test_linkcheck(self):
        import os
        urls_str = " ".join(["http://localhost:8000%s" % url for url in URLS.values()])
        os.chdir(r"c:\django\doukaku_proj")
        os.system("linkchecker -r 1 -q -F html %s" % urls_str)
        os.system("start linkchecker-out.html")
タグにピリオドが含まれているケースで/tag/python2.5/とアクセスするとOKだけど /tag/python2.5とアクセスしたら404、っていうエラーが見つかった。 /tag/hogehogeは404の時に自動的に/tag/hogehoge/じゃないかをチェックするけども、 ピリオドが入っているとチェックしないという理解で正しいだろうか。 こんなの人力では絶対見つけられないよー。 機械的テストは重要だなぁ。
= 体調悪い。 午前中はめまいがずっとしていた。 生産性も高くないので帰ろう。 早めに帰るつもり満々だったのに、 どう書くorgに来ていた提案にむらむらっと来て実装してしまった。 アンド同じところで実装できる前からつけたかった機能をつけてしまった。

本質的に必要な機能ではないのに。 他にもっとやらないといけないことがあるのに。ばかー>自分。

とりあえず帰ってビタミン剤飲んで寝るとします。

2007年07月05日

Django初めて半月くらいの日記

>調子に乗ってお題をいっぱい作っていたらもう27時だ。睡眠時間が足りないぞ。

と言っていたのに7時に目覚めた。 おかしいぞ。 脳が興奮しているぞ。


= メンテしました。 ユーザごとのフィードができたので誰かをこっそりヲチすることもできるし、 特定のユーザに対する返信のフィードもできたので自分に対するコメントにも気づきやすくなりました。

簡単な機能なのにどうしてサポートしてないものがおおいのかな、と考えてみて、 フィードを吐いてユーザに好きにさせる方法では 「あなたをヲチしているのはこの人たちです」 という表示ができないな、と気づいた。 まぁでも、ユーザ間のプライベートメッセージはサポートする気ないし、 そこまでユーザを密結合にする必要もないかなぁと。

投稿傾向で「あなたに一番似ているのはこの人!」ってのはそのうちやるかも。


= プレビューでタグとかが表示されないのがやっぱり気になるらしい。 プレビューでコメントを表示する用のテンプレートは本番表示のテンプレートをincludeして使っている。 本番ではcommentにmodels.Commentオブジェクトが入っている。 しかし、だ。 プレビューの段階でCommentオブジェクトは作れないんだな。 なぜかというとコメントとタグとは多対多の関係を持っているけど、 これって別のテーブルにComment#idとTag#idのセットで入っているモノなので、 Commentだけですむ話じゃなくなってくる。 仮に作ったりすると、じゃぁいつ削除するのかという話。 なのでCommentオブジェクトを作らずにCommentオブジェクトの挙動をエミュレートしたい。

幸い、Djangoのテンプレートエンジンはx.memberがない場合はx["member"]を探しに行く設計になっているので、 辞書で各種オブジェクトをエミュレートできる。 その辞書に適切な値を入れればいいだけ。 いままでtagsとcreate_dateとidは空欄だったけど、下のように入れた。

preview["tags"] = {"all": [{"caption": k} for k in d["tags"].split()]}
from datetime import datetime
preview["create_date"] = datetime.now()
preview["id"] = "??"
tagsがなんか激しいことになっているけども、これはテンプレートで下のように呼ばれているので それにあわせただけ。
  {% for tag in comment.tags.all %}
    {{ tag.caption }}
  {% endfor %}

= レーティングの結果を表示する方法。 とりあえずmodels.Comment.get_ratingを作った。 将来的には一人の評価が余り大きく寄与しないようにする必要があるが、 当初は「自分の評価が反映された感」を出すためにも寄与を大きくする。
    def get_rating(self):
        rs = Rating.objects.filter(comment=self)
        score = dict((k[0], 0) for k in RATING_LIST)
        for r in rs:
            for k in score:
                score[k] += rs[0].__dict__[k] - 2
                print score

        result = [
            {"opt": k[0], "score": max(-2, min(2, score[k[0]])) + 2}
            for k in RATING_LIST]
        return result
で、テンプレートはこう。
{% spaceless %}
{% for x in comment.get_rating %}
<img src="/static/image/{{ x.score }}.png" alt = "{{ x.opt }}"> 
{% endfor %}
{% endspaceless %}

= 「manage test」すると「_("something")」の部分で「String is not callable」 的なエラーが出てテストにならない問題は、増田さんによれば doctestがインタラクティブシェルを使って、 インタラクティブシェルが直前に評価した値を「_」に入れてしまうため、 _がgettextではなくなってしまっているという問題らしい。 unicodeブランチの統合で解決されるらしい。 それまでは
from django.utils.translation import gettext as _
とコードの頭に書いておけばいいそうだ。上書きされてしまった_をもう一度上書きし直す。
= 文字化け問題。

\u4F55\u3089\u304B\u306E\u539F\u56E0\u3067\u524A\u9664\u3067\u304D\u305A\

\uE4\uBD\u95\uE3\u82\u89\uE3\u81\u8B\uE3\u81\uAE\uE5\u8E\u9F\uE5\u9B\uA0\uE3\u81\uA7\uE5\u89\u8A\uE9\u99\uA4\uE3\u81\uA7\uE3\u81\u8D\uE3\u81\u9A

になってる。 んー。。。 なんか後者は三拍子だよねぇ。 \uを\xに置き換えてみる。

>>> unicode("\xE4\xBD\x95", "utf8")
u'\u4f55'
お、やった。機械的に復元する方法がわかった。
= ダメだった。
= IPythonってブロック単位で前のを取ってきたり、できないのかなぁ。 行単位だけなのかなぁ。 こんな方法でコードを書き換えるのはダークサイドのような気がするなぁ。 昔viを起動するのがたるいからsedでデバッグしていた頃のような。
In [24]: for c in g:
   ....:         print >>fo, "c = models.Comment.objects.get(pk=%d)" % c.id
   ....:     answer = re.findall(r"(\\x..)+", repr(c.code))
   ....:     h = repr(c.highlighted_code)
   ....:     for a in answer:
   ....:             h = re.subn(r"(\\x..)+", a, h, 1)
   ....:     print >>fo, "c.highlighted_code = %s" % h
   ....:
---------------------------------------------------------------------------
             Traceback (most recent call last)

C:\django\doukaku_proj\ in ()

C:\Python25\lib\re.py in subn(pattern, repl, string, count)
    150     callable; if a callable, it's passed the match object and must
    151     return a replacement string to be used."""
--> 152     return _compile(pattern, 0).subn(repl, string, count)
    153
    154 def split(pattern, string, maxsplit=0):

: expected string or buffer

In [25]: exec In[24].replace("1)", "1)[0]")

In [26]: fo.close()
25行目がダークサイド。 最終的にこんな感じに「文字化けを修正してくれるスクリプトを作成してそれを実行」
In [32]: g = (c for c in models.Comment.objects.all() if "\\x" in repr(c.highli
hted_code))

In [33]: fo = file("tmp.txt", "w")

In [34]: exec In[24].replace("1)", "1)[0]").replace(" % h", " % h; print >>fo,
c.save()'")

In [35]: fo.close()

In [36]: execfile("tmp.txt")
うーん。 パワフルなのはパワフルなんだけどなぁ。 ダークサイドだよなぁ。

まぁよい。 これで後は本番サーバを一旦止めてデータベースを持ってきて、 このスクリプトを走らせてから返せばいい。


= とりあえず初テスト
Ran 30 tests in 131.375s

FAILED (failures=3, errors=6)
あれれ。

ああ、Userテーブルはauthのほうにあるから、そっちもdumpしてfixtureをつくんないと。

manage.py dumpdata auth --indent=2 > initial_data.json
Ran 30 tests in 162.656s

FAILED (errors=1)
減った。 最後の一個。
InterfaceError: Error binding parameter 0 - probably unsupported type.
これはプロフィール編集画面にログインしていない状態で行こうとするとこうなる。

ログインするように変えた。 今は30個のページにGETして200が帰ってくるかどうかしかテストしてないけど 自動で試せるから楽。 言語ごとのフィードがバグっているのを発見できたし。

from django.test import TestCase

OK = 200
URLS = dict(
    (url.replace("/", "_"), url)
    for url in (
      "/ /user/ /comment/download/41/ /comment/41/rating/ "
      "/comment/41/addtag/ /5/feedback/ /tag/gauche/ "
      "/lang/python/ /feedback/ /feeds/latest/ "
      "/feeds/challenges/ /feeds/comments/ /feeds/forlang/python/ "
      "/feeds/postedby/1/ /feeds/repliedto/1/ /challenge/ "
      "/lang/ /tag/ /comment/ /user/ /1/ /1/nested/ /1/flatten/ "
      "/user/1/ /comment/41/ /terms/ /qa/ /policy/ /tos/").split())

def isOk(self, url):
    self.failUnlessEqual(
        self.client.get(url).status_code, OK)

class IsOkTest(TestCase):
    for k in URLS:
        locals()["test_" + k] = lambda self, k=k: isOk(self, URLS[k])

    def test_login_and_edit_profiles(self):
        self.client.login(user="guest", password="guest")
        isOK(self, "/editprofile/")
30個のテストに4分かかるけど、これは仕方ないのかなぁ。
= POSTのテスト作るの面倒だよね。 そこでこんなビューを書いてみました。
def print_post(request):
    print 
    print "    def test%s(self):" % (
        request.path.replace("/", "_"))
    print "        isOk(self, %s, %s)" % (
        repr(request.path),
        dict(request.POST))
    print 

    return HttpResponseRedirect("/")
urls.pyには冒頭のほうでこう書きます。
    (r'^$', views.top_page),
    (r'^.*$', views.print_post),
僕の場合こう書くと、トップページと画像・スタイルシートなど以外へのアクセスが 全部veiws.print_postの方へ行くようになります。

で、書いておいて一旦コメントアウトします。 そしてPOSTする直前まで操作を進めます。 タブブラウザでたくさん開くと吉。

でコメントアウトを外して全部キャプチャするようにした上でPOST。 そうするとこんな感じの出力が出ます。

    def test_comment_90_addtag_(self):
        isOk(self, '/comment/90/addtag/', {'tags': ['a \xe3\x81\x82']})
後はテストケースのクラスにぺたぺた貼り付けるだけ。

タグをつけようとするとエラーが出るようになっていることに今気がついた。 例のis_valid()呼ばないといけない問題。


= FKがNULLでない物の個数を数えたかったんだけど、 filter(key=None)じゃダメなのか。

__isnull=Falseを使う。 ユーザを指定されたときに、そのユーザの投稿のうち言語の指定のあるものの個数を返すコード。

def num_code(self):
    cs = Comment.objects.filter(author=self)
    return cs.filter(lang__isnull=False).count()
User.num_code = num_code    

= Googleでヒットしなくなった。
= 金曜日。

グリニッジ標準時で保存されているデータを日本標準時で表示したいという話、 日本語のサイトだからといって見ている人のタイムゾーンが日本標準時とは限らないので どうしようかと悩んでいた。ユーザに設定させてクッキーか何かに保存しておくか、とか。

JavaScriptでできるよ、と奥さんに教えてもらったのでさっそく実装。

function print_time(d){
  function fill_zero(x){
    x = x.toString();
    if(x.length == 1){
      x = "0" + x;
    }
    return x;
  }

  document.write(
    (d.getYear() + 1900) + "-" +
    fill_zero(d.getMonth()) + "-" +
    fill_zero(d.getDate()) + " " +
    fill_zero(d.getHours()) + ":" +
    fill_zero(d.getMinutes())
  )
}

print_time(new Date("2001/01/02 0:00 GMT")); 
これで僕のブラウザで見ると09:00と表示される。

Python プログラマのための Django テンプレート言語ガイド : Django オンラインドキュメント和訳を参考に。

from django import template
register = template.Library()

@register.filter
def jstime(datetime):
    gmt = datetime.strftime("%Y/%m/%d %H:%M GMT")
    return """
    <script language="JavaScript">
        print_time(new Date("%(gmt)s"));
    </script>
    <noscript>
        %(gmt)s
    </noscript>
    """ % locals()
これで{{ comment.create_time|jstime }}って書くだけで適当なタイムゾーンで表示してくれる。 JavaScriptにバグがあり、IEだと1900がすでに足された状態なので 1900以上かどうかをチェックするようにしました。あとgetDayじゃなくてgetDateを使うべきでした
= OliverGraf/VimColor - MoinMoin。 VimColorのPython版。
= なんか猛烈に疲れた感があるんだけど、よく考えてみると
3つ前の週末:休日出社してプレゼン準備
2つ前の平日:プレゼン→Python温泉に向けてDjangoDjango
2つ前の週末:朝までDjango
1つ前の平日:Workshopまでにプロトタイプを公開したいと頑張る
1つ前の休日:徹夜でプレゼン資料→ワークショップで発表
今週の平日:プロトタイプを少人数でベータテストのつもりだったのにすごいアクセスに!
      必死でデバッグ←今ここ
すごい。

疲れた脳からはクリエイティビティは沸いてこない。 週末は休みたい。 (週末は未踏ユースのブースト会議だ…あとJython本…)

2007年07月02日

Django14日目日記

以前はまって、はまっていることに気づかずに進んだら後で困ったことになった罠。

現在のDjango0.97preで、 明らかにvalidなデータしか来ないようなフォーム (required=Falseの長さ無指定のテキストフィールドだけとか) でもis_validは呼ばなければいけない。

cleanは実際には何もcleanしない。 is_validがcleanしている。 なので 「あー、このフォームは常にis_valid==Trueだからif文いらないや」と削ると cleanがエラーを出すようになり 「じゃぁPOSTから値を取って使おう」 とやるとお行儀の悪いブラウザがUTF-8のフォームなのにUTF-8以外で送ってきたときに ややこしいことになったりしそう。

ややこしいことを避けるためには、 自分でPOSTの値に触るのはなるべき避けなければいけない。 どんなときでもis_validを呼ばなければいけない。 (特に文字列の場合。BooleanFieldとかだと触っても大丈夫そう。)


= Djangoのドキュメントはymasudaのところよりmichiluのところが新しいのだけども、 いつも「Django ほげほげ」でググって資料を探す僕みたいなのはymasudaの方にたどり着いてしまう。 いまどういうシステムで Djangoのドキュメントは日本語で全部読める という状態を維持しているのか、知らない人も多いと思うので、 日本のDjangoに興味のある人が新しい方のドキュメントにたどり着けるように michilu.comをSEOしたらいいんじゃないかと思った。

というわけでとりあえず Django オンラインドキュメント和訳。 みんなでやったら効果あるかなぁ?


= あ、そうか、 doukakuの僕宛フィードバックがadminのページで開けなくなっている問題は、 is_validしないで書き込んでいることが原因かも。

ふむふむ。

ちがうっぽいなぁ。 dumpしたらきちんとした値になっているし、 SQLite Database Browserで見てもちゃんと フィールドに値が入っているのに、 Django adminの吐くエラーは… あっ、単純にモデルの__str__でみすってるだけだった。


= コメントのツリー表示をテンプレートの再帰呼び出しで実現
# module_comment_with_follow.html
{% include 'doukaku/module_comment.html' %}
{% for comment in object.comment_set.all %} {% include 'doukaku/module_comment_with_follow.html' %} {% endfor %}
しようとしたらダメだった。無限ループ。 ふーむ。 forの中身が空っぽでもincludeしちゃうのか。
= どうもincludeタグをIncludeNodeオブジェクトにする(コンパイル)際に、 ファイル名からテンプレートを解決して、 そのテンプレートの中のincludeタグをさらにIncludeNodeにするそうで、 レンダリング時にforが空でrenderされないかどうかにかかわらず 自分自身をincludeするテンプレートは無限ループになってしまう。

しかたがないので自作テンプレートタグで解決しようと思った。 とりあえず参考文献: Python プログラマのための Django テンプレート言語ガイド : Django オンラインドキュメント和訳

「テンプレートシステムの拡張」のところ。 まずtemplatetagsフォルダを作る。

とやっているときにDjango-ja Lingrで増田さんが一言 「includeの引数を変数にしてみると幸せかもしれないぞ」

おおお、そ