Displaying posts filed under

Python

Google App Engine習作: Thumbs.db reader

Google App EngineでWindows XPのThumbs.dbを読み出すWebサービスを作りました。

Thumbs.db reader

アップロードされたThumbs.dbを解析して、サムネイル画像(JPEGファイル)を取り出します。結果はdata:スキームを使ってimgタグで一覧表示するか、zipでまとめて取得するか選べます。アップロードされたファイルはサーバ側には保存されません。

Thumbs.dbの解析にはvinettoというプログラムを改造して使っていますが、GAEの制約によってtype 2という形式のものしか解析できません。

コマンドラインから使用する場合は http://thumbsdbreader.appspot.com/zip というURLに対してPOSTリクエストを送信してください。zipファイルが標準出力に出力されます。

curl -X POST --data-binary @Thumbs.db http://thumbsdbreader.appspot.com/zip > files.zip

Google App EngineでMakoを使う

Google App EngineでテンプレートエンジンMakoを使う方法……といっても、特別なことは何も必要ない。アーカイブを展開してできた mako ディレクトリをアプリケーションディレクトリにコピーすれば使えるようになる。

日本語を使う場合の注意点についてはこちらを参照のこと。GAEではRequestもResponseもDatastoreもUnicodeに対応しているので、あまり神経質にならなくても大丈夫。

コード例

  • テンプレートファイルは views/ ディレクトリに置く
  • デフォルトの拡張子は .html
  • エンコーディングは utf-8 固定

次のようなベースクラスを用意しておく。

from mako.template import Template
from mako.lookup import TemplateLookup
 
class RequestHandler(webapp.RequestHandler):
    viewDir = os.path.join(os.path.dirname(__file__), "views")
    lookup = TemplateLookup(directories=[viewDir], input_encoding='utf-8')
 
    def render(self, view, vars={}, type=".html"):
        tmpl = self.lookup.get_template(view + type)
        return tmpl.render_unicode(**vars)
 
    def display(self, *args, **named):
        self.response.out.write(self.render(*args, **named))

サブクラスでは display または render メソッドでレンダリングを行う。

class MainPage(RequestHandler):
    def get(self):
        self.display('index', {"var": "value"})

テンプレートエンジンMakoで日本語を使う

PythonのテンプレートエンジンMakoで日本語を使う方法まとめ。

テスト環境: Python 2.5, Mako-0.3.2

Makoの内部処理はunicodeで行われる。そのため入出力時にエンコーディング指定を正しく行い、テンプレート変数の展開時にunicodeオブジェクトに変換することが必要になる。

入力時

テンプレートファイルが読み込まれる際に、ファイルで使用されている特定のエンコーディング・スキーム(utf-8など)に基づいて、unicode オブジェクトへのデコードが行われる。

最も優先順位の高い指定は、テンプレートファイル先頭行の magic encoding comment である。この一行はテンプレートの内容に含まれないので、HTMLなどに書いても問題ない。

## -*- coding: utf-8 -*-
<p>まこまこ</p>

次に優先されるのは Template コンストラクタにおける input_encoding の指定である。

from mako.template import Template
 
tmpl = Template(filename="view.html", input_encoding="utf-8")

そして最も優先順位が低く、広範囲に適用されるのが TemplateLookup のコンストラクタにおける input_encoding の指定である。実際のアプリケーションで最も良く使われるのがこれだと思われる。

from mako.lookup import TemplateLookup
 
lookup = TemplateLookup(directories=["views/"], input_encoding='utf-8')
tmpl = lookup.get_template("index.html")

出力時

render メソッドを使ってレンダリング結果を取り出す際に、Mako内部の unicode オブジェクトが str オブジェクトにエンコードされる。このとき使用されるエンコーディングは Template または TemplateLookup の output_encoding で指定できる。

## coding: utf-8
from mako.template import Template
 
tmpl = Template(u"まこまこ", input_encoding="utf-8", output_encoding="utf-8")
out = tmpl.render()
print len(out)  # => 12
print type(out) # => <type 'str'>

しかし render_unicode メソッドを使えば unicode オブジェクトをそのまま取り出すことができる。出力先が unicode に対応しているのなら、これをそのまま使うのが最もトラブルが少ないと思う。

tmpl = Template(u"まこまこ", input_encoding="utf-8")
out = tmpl.render_unicode()
print len(out)  # => 4
print type(out) # => <type 'unicode'>

もちろん必要なら取り出したunicodeオブジェクトの encode メソッドを使うこともできる。

tmpl = Template(u"まこまこ", input_encoding="utf-8")
out = tmpl.render_unicode().encode('euc-jp')
print len(out)  # => 8
print type(out) # => <type 'str'>

式展開

テンプレート内の式は自動的に unicode オブジェクトに変換されるため、非ASCII文字を含む可能性のある str オブジェクトを使用する場合は、手動で適切なエンコーディングを指定してデコードしなければならない。

<p>${strobj.decode('utf-8')}</p>

まとめ

  • TemplateLookup の input_encoding で最も一般的なエンコーディングを指定しておく。必要に応じて Template の input_encoding や magic coding comment を使用する。
  • 可能な限り render_unicode を使用する。
  • 非ASCII文字を含む可能性のある str オブジェクトをテンプレート内で使用する場合は、手動でデコードする。

参照

Pythonでリストのflatten・高階関数を使って

Python には組み込みの flatten がない。そこで次のように実装した。

def traverse(f, x):
    if isinstance(x, (list, tuple)):
        for e in x:
            traverse(f, e)
    else:
        f(x)
 
def flatten(listOfList):
    arr = []
    traverse(arr.append, listOfList)
    return arr

Python flatten」でググると様々な実装例が見つかるのだが、妙に凝った実装が多くて、こういうシンプルな実装はあまり見当たらなかった。

ただしこの実装は再帰を使っているので、ネストが深すぎるとオーバーフローする。

x = []
for i in xrange(1000):
    x = [x, i]
print flatten(x)
# => RuntimeError: maximum recursion depth exceeded

オーバーフローを避けるには traverse をループを使って書き下す。Pythonでは末尾再帰の最適化は行われない。

def traverse(f, x, isSeq = lambda y: isinstance(y, (list, tuple))):
    if not isSeq(x): return f(x)
    pos = depth = 0
    stack = { depth: [x, pos, len(x)] }
    while depth >= 0:
        lis, pos, size = stack[depth]
        if pos < size:
            stack[depth][1] = pos + 1
            if isSeq(lis[pos]):
                depth += 1
                stack[depth] = [lis[pos], 0, len(lis[pos])]
            else:
                f(lis[pos])
        else:
            depth -= 1

これで100000とかネストしても平気。

x = []
for i in xrange(100000):
    x = [x, i]
print flatten(x)
# =>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
...省略...
, 99977, 99978, 99979, 99980, 99981, 99982, 99983, 99984, 99985, 99986, 99987, 99988, 
99989, 99990, 99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999]