2010年11月30日火曜日

開発進捗(11/30)

htmlのレンダリング部分はだいぶ満足がいくようになってきた。
あとは、form関連のテンプレート化をしたい:
 ・リクエストからの変数の取得
 ・入力内容のチェック
 ・エラー時の動作
 ・htmlのキャッシュ
 ・レスポンスの作成:
   ・キャッシュ期間の指定
   ・文字コードの指定

ほかにやりたいこと:
 その1、BigTableへの操作のテンプレート化:
  ・検索時のキャッシュの利用
  ・更新時にタスクキューを使う
  ・メンテナンス用のフォームの生成
 その2、よく使いそうな動作のテンプレート化:
  ・掲示板機能
  ・ログイン・ログアウト・パスワードの変更
  ・ユーザ登録
  ・定期ジョブによるデータ削除

やれることはいっぱいあるなぁ。

それにしても、テストケースを作成しておくと、
本当にリファクタリングが楽ですね。

2010年11月19日金曜日

Pythonのシングルトン

httpレスポンス作成で、htmlテンプレートを毎回生成するのが微妙なので、
シングルトンについて調べてみる。

hasatttrは遅そうなので、try~exceptで例外をキャッチする方法にしたところ、10%高速になった。

class Singleton(object):
    def __new__(cls, *args, **kwds):
        try:
            return cls.__instance
        except AttributeError: 
            instance = super(Singleton, cls).__new__(cls, *args, **kwds)
            cls.__instance = instance
        return instance

そのままだと、__init__(self, *vals, **kwds)が、毎回呼ばれてしまうので、
__init__を上書きする処理を追記。
さらに、__slots__ = ()を入れて、1%高速化。


class Singleton(object):
    __slots__ = ()
    @staticmethod
    def _nodo(self,*args, **kwds):
        pass
    def __new__(cls, *args, **kwds):
        try:
            return cls.__instance
        except AttributeError: 
            instance = super(Singleton, cls).__new__(cls, *args, **kwds)
            cls.__instance = instance
            cls.__init__(instance, *args, **kwds)
            cls.__init__ = Singleton._nodo
        return instance 

2010年11月17日水曜日

Python:親クラスへのアクセスする手段について

子クラスから親クラスのメソッドを使う場合、
superより、親クラスを指定するほうが13%くらい速いみたいですね。

多重継承の場合や、階層が多段の場合は、面倒なので未調査です。
でもたぶん親クラスを直に指定したほうが速そうですね。

今はsuperを使っているのですが、
固まってきたら親クラス指定に変えようかな。


from timeit import Timer
setup = u"""
class Test1(list):
    def __init__(self,*vals,**kwds):
        list.__init__(self,*vals,**kwds)

class Test2(list):
    def __init__(self,*vals,**kwds):
        super(Test2,self).__init__(*vals,**kwds)

a=['a']
"""

do1 = u"""
Test1(a)
"""

do2 = u"""
Test2(a)
"""

time1 = Timer(do1, setup).repeat(20, 2000)
time2 = Timer(do2, setup).repeat(20, 2000)
persent = (sum(time1)) * 100 / (sum(time2))

print str(persent) + '%'

86.4219560341%

2010年11月16日火曜日

Pythonのコーディングスタイル(PEP8)をチェック

参照元

なるほどー。そんな便利なコマンドがあったとは。
さっそく、nosetests起動用バッチコマンドに組み込みました。

1.pep8チェック
2.pep8チェックでエラー無:nosetestsを実行して終了。
3.pep8チェックでエラー有:pauseしてからnosetestsを別ウィンドウで実行。並行して1に戻る。

そのうち公開します。

2010年11月10日水曜日

Google App Engine for Python:データストアにデータが保存されない?

初歩的なミスで、1日ほどはまってました。

間違いソース:
 class Usersa(db.Model):
  userid = db.StringProperty(required=True, multiline=False)

 one_record = DataModel(userid=userid)
 one_record.put

正解ソース:
 class Usersa(db.Model):
  userid = db.StringProperty(required=True, multiline=False)

 one_record = DataModel(userid=userid)
 one_record.put()


putとput()は、大違い。
putは、putメソッドへの参照。
put()だと、putで指されているメソッドの実行。

蛇さんは、すっきりした書き方ができるのが魅力ですが、
その冗長性の低さは、気をつけないとダメですね。

2010年11月5日金曜日

pythonの文字列編集

いちおう、個人的な結論が出ました:
 普通のサイズなら+=で十分。listを育てて''.join(list)するのはナンセンス。
 多少大きいなら、''.join([i for i in ジェネレータ])
 本当に大きいなら、''.join(ジェネレータ)

ジェネレータをリスト内包表記でリストにしたほうが たいてい 速いというのは、意外な結果でした。

以下が検証につかったプログラムです。

from timeit import Timer

#リストを育てて、join
str_test1 = """
for num in _gen:
    str_list.append(num)
_join(str_list)
"""

#stringIOに追記
str_test2 = """
for num in _gen:
    file_str.write(num)
file_str.getvalue()
"""

#charのarrayに追加
str_test3 = """
for num in _gen:
    char_array.fromstring(num)
char_array
"""

# += で 育てていく
str_test4 = """
for num in _gen:
    out_str += num
out_str
"""

# リスト内包表記をjoin
str_test5 = """
ret = _join([i for i in _gen])
ret
"""

# ジェネレータを直にjoin
str_test6 = """
ret = _join(_gen)
ret
"""

setup = """
loop_count = 30
out_str = ''
from array import array
char_array = array('c') 
str_list = []
from cStringIO import StringIO
file_str = StringIO()
_join = ''.join
_name = 'test'
_gen=('<test' + ' name="' + _name + '"' + '>' + `num` + '</test>' for num in xrange(loop_count))
"""

print Timer(str_test1, setup).repeat(3, 100)
print Timer(str_test2, setup).repeat(3, 100)
print Timer(str_test3, setup).repeat(3, 100)
print Timer(str_test4, setup).repeat(3, 100)
print Timer(str_test5, setup).repeat(3, 100)
print Timer(str_test6, setup).repeat(3, 100)


loop_count=30
[0.00024053336528595537, 0.00023634288663743064, 0.00023606352260685526]
[0.00015420954332512338, 0.0001477841469750274, 0.00015532700490439311]
[8.7441281721112318e-005, 8.353016892215237e-005, 8.2692073192447424e-005]
[7.2076200012816116e-005, 6.7326993303140625e-005, 6.7885723183280788e-005]
[8.0177787822321989e-005, 7.5987310992786661e-005, 7.5707945143221878e-005]
[0.00047128894948400557, 0.00047352387082355563, 0.00047156831351458095]


# +=が一番速い!
# ''.join(ジェネレータ)は一番遅い!


loop_count=300
[0.0019653335821203655, 0.0020566859766404377, 0.0019753907254198566]
[0.00092833027701999526, 0.00092358107031031977, 0.00092413980019045994]
[0.00075568263673631009, 0.00067997468977409881, 0.00062019055440032389]
[0.00051794292267004494, 0.00050229847693117335, 0.00050257784096174873]
[0.00045676196350541431, 0.00043832386472786311, 0.00044111751776654273]
[0.00082524454956001136, 0.00080149851419264451, 0.0007970286733325338]
# ''.join(i for i in ジェネレータ)の順位が上昇。一番速い!
# ''.join(ジェネレータ)の順位が上昇。


loop_count=3000
[0.024607876141089946, 0.024106695123919053, 0.023980701458640397]
[0.017470097456680378, 0.019163050053975894, 0.021411380497738719]
[0.009339734519016929, 0.0092833027665619738, 0.0093380583293765085]
[0.0081722677041398128, 0.0050972958852071315, 0.0080621978486306034]
[0.0041698037039168412, 0.0040753782959654927, 0.0040725846429268131]
[0.0043092068972327979, 0.004270654510037275, 0.0042558481600281084]
# ''.join(ジェネレータ)が2位に。


loop_count=30000
[0.32366962840205815, 0.33386114715540316, 0.33299315974545607]
[0.19254905302113912, 0.19246356729672698, 0.19229231648205314]
[0.18879075413315149, 0.14344420869201713, 0.18765234128841257]
[0.13475986473167723, 0.16792050386175106, 0.1337812487345218]
[0.054552184705244144, 0.055729149933540612, 0.055441683231038041]
[0.046381872556594317, 0.05237118125478446, 0.052848616234768997]
# ''.join(ジェネレータ)が1位に。
#  でも、''.join(i for i in ジェネレータ)とあんまり変わらない。なぜに?


loop_count=300000
[3.215461741645413, 3.1868516808699496, 3.1819172548475763]
[2.0470207805756218, 2.0494777967596747, 2.0539613274868316]
[16.015020065398858, 16.072113355188776, 16.03753214444987]
[15.98263103271529, 15.960840553758317, 15.985554309276267]
[0.47812361627075006, 0.46673641482448147, 0.48726667774826637]
[0.46456016057891247, 0.45079220962543332, 0.46201709993874829]
# += と char_array が、一番遅くなった。

2010年11月4日木曜日

開発進捗(11/4)

下の調査結果を元に、htmlの生成部分のリファクタリングをしてました。
・無理に''.joinは使わない。
・速度を理由に文字を定数化しない。

明日は、ログインフォームを作ろう。

Pythonの文字列結合のパフォーマンス

Python2.5.4で、文字列結合の速度についていろいろ試してみました。

あらかじめb='b'、c='c'となっている場合:
a='b'+'c'
a=b+c

これはどちらも、ほぼ同じ時間でした。
変数に入れているほうが、オブジェクトの生成がなくて速そうですが、意外にもそうではありませんでした。

a=''.join(['b','c'])
a=''.join(('b','c'))

当然、下が速いです。リスト内包表記を使わないなら、タプルを使ったほうが良いですね。


あらかじめ_join=''.joinとなっている場合:
a=''.join(('b','c'))
a=_join(('b','c'))

これも、下が速いです。3回以上''.join(iter)を使うようでしたら、変数に入れておいたほうが速かったです。

あらかじめ_join=''.joinとなっている場合:
a='b'+'c'
a=_join(('b','c'))

これは、上のほうが速かったです。文字列が長かったり、何度も結合する場合は、_join(iter)のほうが速いはずなのですが、それは調査中です。

2010年11月3日水曜日

開発進捗

''.join(list)のlistは、イテレート可能であればlistで無くても良いみたい。
listに間違ってジェネレータをそのまま指定したら、そのまま動いたので気がつきました。
試行錯誤した結果、''.join(ジェネレータ)で、単純な文字結合の約1.3倍の速度になりました。(ローカル環境での実測。本番環境ではどうなのかは未検証)

unittestのテストケースのメソッドにコメントを入れたら、noseの出力結果にコメントが出力された。
そんな小便利な機能があるのですね。
ただ、通常は「メソッド名(テストケースのクラス名)」と表示されたところが、「コメント」と出力されるので、どこの結果なのかがわかりにくいと思いました。
半端にコメントいれると、かえってまずそうなので、今はコメントを入れない方向です。

2010年11月2日火曜日

開発進捗とか

今何している?:
・Htmlを生成する俺ライブラリを作成中:
・最近気がついてションボリ:
・標準ライブラリのxml.etree.ElementTreeと中身がほとんど被っていた!
・でも俺ライブラリのほうが表記が短くて済むのでよしとする。
・それにちらっと覗いた感じでは標準ライブラリも大した事をやっていない。
・xml文字列作成部分では、高速化する余地がありそう。

今後何をしたい?
・Htmlを生成する俺ライブラリの改善:
・サービスを広げようと思うと、やっぱり速くしたい。
・文字列の結合は、やっぱり''.join(list)が速いみたい。
・イッシー環境では、listをリスト内包表記にすると、小さな文字列でもだいぶ違った。
・listを育ててから''.join(list)すると、あんまり差が出ないことも多い。
・html文字列生成時は、今は文字列連結でやっているので、高速化の余地がある。
・リストを育てていくと、リストの再生成で時間がかかるはずなので、
全体をなめるイテレーター処理を定義して、それをリスト内包表記にして、join(list)とさせよう。
・html文字列生成時の文字コードの指定は、内部的にはグローバル変数を介してやろう。
そのほうが引数のやり取りが減って速そう。
・エスケープ処理がないので改善が必要
・Formのhtmlは作成できるけど、それだけ。
・ブラウザから返ってきたrequestデータをパースしてFormで受け取る処理が欲しい
・スタイルクラスをうまく定義できるようにしたい
・responseのヘッダーに文字コードを設定する処理をうまくやりたい
・ログインフォームの作成:
・具体的に使ってみないと、ライブラリの使い勝手がわからない
・テーブル操作もやりたい
・そろそろセッション維持についても実装しながら考えたい

ここ数ヶ月を振りかえって思ったこと:
・テスト駆動開発はすばらしい。リファクタリングが思う存分出来る。
・不必要に下調べはしないほうが良い。調べたことを忘れてしまう。
すぐに実装しようと思っていることに対してのみ調べるべき。
・フレームワークは、一度自作すると良い。
ほとんどが既存の仕組みで代用できますが、勉強になる。
そうして初めて、既存のフレームワークのありがたさが良くわかる。

2010年11月1日月曜日

WindowsXP(eclipse)でGAE/Pのテスト駆動開発(nose)

WindowsXP環境でeclipse(PyDev)を使ってGoogle App Engine の開発をしているのですが、
・テストコードを簡単に実行したい
・エラーの有無を実行結果の色で判別したい
という思いから、以下のようなバッチを作ってみました。
 <nosetest.bat>
@ECHO OFF
CD /D %~dp0
REM ========================================
REM nosetests 起動バッチ
REM   ①nosetests にパラメータを指定して起動、出力結果を一時ファイルに保存
REM   ②テストでエラーなし:テスト結果を標準出力に出力
REM     テストでエラーあり:テスト結果をエラー出力に出力
REM   ③30秒間(パラメータ1で変更可能)そのままポーズして終了
REM ========================================
REM 起動パラメータ:
REM     パラメータ1=指定された秒数だけ表示(デフォルトは30秒)
REM     パラメータ2=nosetestsの追加パラメータ
REM ========================================

IF "%1"=="" (SET WAIT_TIME=30) ELSE (SET WAIT_TIME=%1)
SET EXTENDS = %~2
SET /A WAIT_TIME=%WAIT_TIME%+1 >NUL

REM ======================================
REM 内部パラメータ:
REM     EXE_COMAND=nosetest.exeの起動コマンド(必要に応じてpathも指定)
REM     SRC_PATH=ソースディレクトリの相対パス
REM     NOSETESTS_RESULT_FILE=noseの実行結果の保存ファイル
REM ======================================

SET EXE_COMAND=nosetests.exe -v --with-gae --gae-lib-root="C:\Program Files\Google\google_appengine" --without-sandbox
SET SRC_PATH=..\src
SET NOSETESTS_RESULT_FILE=nosetests_result.txt

REM ======================================
REM 実処理
REM ======================================

SET EXE_COMAND=%EXE_COMAND% %EXTENDS%
TITLE %EXE_COMAND%

CD /D %SRC_PATH%
%EXE_COMAND% 2>%~dp0\%NOSETESTS_RESULT_FILE%
CD /D %~dp0
FINDSTR "FAILED (*" %NOSETESTS_RESULT_FILE% > NUL
SET ERROR_CODE=%ERRORLEVEL%

IF "%ERROR_CODE%"=="0" (color 4F & type %NOSETESTS_RESULT_FILE% 1>&2 & SET RETURN_CODE=1) ELSE (color 27 & type %NOSETESTS_RESULT_FILE% 2>&1 & SET RETURN_CODE=0)
@PING -n %WAIT_TIME% localhost >NUL
EXIT /B %ERROR_CODE%

動作確認環境:WindowsXP SP3 + eclipse3.5.2 ( +PyDev 1.6.3 + Nose 0.11.4+ NoseGAE 0.1.7)

配置はこんな感じを前提にしてます。
[workspace]\[project]\bat\nosetest.bat
[workspace]\[project]\src\app.yaml
[workspace]\[project]\src\app.yaml
[workspace]\[project]\src\main.py
[workspace]\[project]\src\test_main.py
[workspace]\[project]\src\htmlgenerator\base.py
[workspace]\[project]\src\htmlgenerator\test_base.py
使い方:
その1:
  外部ツールにnosetests.batを登録、起動時のパラメータに0を指定。
  登録した外部ツールを、一度実行。
  「前回実行した外部ツールを起動」のメニューに、Ctrl+Tを割り当て。
 その2:
  ショートカットを作成して、スタートメニューに登録。
  Windowsキーを押してスタートメニューを表示させて、Nを押す。