埋め込みPythonを実装してみました

更新履歴

  • 2009/02/20 version 1.0.0

    • RendererHelperを追加。詳しくはソースファイルヘッダ部分のドキュメントを参照してください。
  • 2009/02/17 version 0.5.0

    • <%= %>で自動的にフィルタを適応できるようになりました。また、renderメソッドがunicodeオブジェクトではなくunicodeのサブクラスEmbpyStringオブジェクトを返すようになりました。filterはEmbpyStringオブジェクトをスルーします。これにより2重でfilterが適応されることがなくなります。<%=r %>でフィルターをオフにできます。

      python code
      1. Embpy("<%= b %>", filter=cgi.escape).render({"v":"<b>"})
      2. # => "&lt;b&gt;"
      3. Embpy("<%=r b %>", filter=cgi.escape).render({"v":"<b>"})
      4. # => "<b>"
      5.  
      6. result = Embpy("<%=r b %>", filter=cgi.escape).render({"v":"<b>"})
      7. # result.__class__ => EmbpyString
      8. Embpy("<%= b %>", filter=cgi.escape).render({"v":result})
      9. # => "<b>"
      10.  
    • re.Scannerscanメソッドがスレッドセーフだったのでインスタンスをモジュールグローバルにしました。また、re.Scannerインスタンスの初期化をLazyにしました。このことによりre.Scannerインスタンスの生成数が減り、さらにキャッシュのみの利用時にはインスタンスを生成しないのでパフォーマンスが向上しました。

  • 2009/02/16 version 0.4.0

    • パフォーマンス改善
  • 2009/02/15 version 0.3.0

    • epyの中の人から「end.. orz」という反応をいただいたので、endの代わりに<% %>も使えるようにしました。

       code
      1. <%- if True: -%>
      2.    ok
      3.    <% %>
      4.  

    と書けるようになりました。

  • 2009/02/14 version 0.2.0

    • "{" と "}"が辞書リテラルとかぶっていたので、"{:"と":}"に変更しました。
    • 変換後コードでなく、コンパイル済みcodeオブジェクトをキャッシュするようにしました。

はじめに

このブログはweb.pyで作られており、テンプレートエンジンもweb.py標準のものを使っています。でもこのweb.pyのテンプレートエンジン、罠が多い。なので他のテンプレートエンジンに置き換えようかなあ、とか思ってました。

んで個人的にはわざわざテンプレート用に文法覚えるのはめんどいので、埋め込み形式でコードが短くて軽そうなのはないかと探したところ、epyがヒット。

ただ、この実装%>が文字列の中にあると動かなかったり(a= "hoge%>"みたいな)、コードの短さゆえに割り切っている部分が多いので同じくらい短いコードでもうちょっと高機能版を実装してみました。以前紹介したre.Scannerを活用すれば、見やすいコードで短く実装できました。

  • キャッシュ
  • インラインでPythonを書くことも出来る: def format(v) {: return "%4d"%v; :}みたいに。
  • eRubyのtrim modeの"<%-"と"-%>" : これがあると無いではテンプレートの見易さが段違い。
  • 自動的にフィルタを適応。しかも2重でフィルタが適応されない。
  • もちろんマルチバイトでも大丈夫。

といったところが特徴ですかね。

テンプレートはこんな感じにかけます。

 code
  1. <%-
  2. class Hoge(object):
  3.   def __init__(self):
  4.     pass
  5.   end
  6. end
  7. hoge = Hoge()
  8. a = "< title >"
  9. -%>
  10. <%=r a %>
  11. <%- def format(v) {: return "%4d"%v; :} -%>
  12. <%- def format2(v) {: return "%2d"%v; :} -%>
  13.   <% for y in xrange(1,xx):%><%= format(y) %><% end %>
  14. <%- for x in xrange(1,xx): -%>
  15. <%= format2(x) -%>
  16.   <%- for y in xrange(1,xx): -%>
  17. <%= format(x*y) -%>
  18.   <%- end %>
  19. <%- end -%>
  20.  

んでこんな感じに使います。第1引数にはファイルではなく文字列も渡せます。

python code
  1. e = embpy.Embpy(codecs.open("path_to_template", encoding="utf8"),
  2.           cache_path = "path_to_cache_file",
  3.           template_globals = {}, filter=cgi.escape)
  4. print e.render({"xx": "10"})
  5.  

出力はこう。

 code
  1.     1 2 3 4 5 6 7 8 9
  2. 1 1 2 3 4 5 6 7 8 9
  3. 2 2 4 6 8 10 12 14 16 18
  4. 3 3 6 9 12 15 18 21 24 27
  5. 4 4 8 12 16 20 24 28 32 36
  6. 5 5 10 15 20 25 30 35 40 45
  7. 6 6 12 18 24 30 36 42 48 54
  8. 7 7 14 21 28 35 42 49 56 63
  9. 8 8 16 24 32 40 48 56 64 72
  10. 9 9 18 27 36 45 54 63 72 81
  11.  

ダウンロード

embpy

コード

先読みはいらないので、re.Scannerで一発。

あと"(((?<=\\)")|[^"])\*((?<!\\)")"という正規表現は自分的には常套句。"で囲まれていて\"は"自身を表す、というよくある文字列の仕様に使える正規表現です。

01.06.10/12am

XREAで好きなバージョンのPythonを使う方法2

やんごとなき事情によりxrea内でWEBサーバを移動しました。

というわけで、このブログ(web.pyによる自作ブログ)を移したわけですが、今までのようにバイナリ化して動かすにはサーバと似た環境が手元にないといけません。が、移動した先のサーバでは環境がだめ。

ということで、python2.5をxreaにインストールしました。virtual-pythonもいいんですが、xreaのサーバはpython2.4なので。ちょっと工夫すれば入るし、快適ですね。以下作業ログ。

Pythonをインストール

まずはPythonのソースをダウンロードしてコンパイル。~/root/usr/localにいれます。

 code
  1. mkdir -p ~/root/usr/local/src
  2. cd ~/root/usr/local/src
  3. wget http://www.python.org/ftp/python/2.5.4/Python-2.5.4.tgz
  4. tar zxvf Python-2.5.4.tgz
  5. cd Python-2.5.4
  6. ./configure --prefix=~/root/usr/local
  7. make
  8. make install
  9.  

サクっと入ります。で、次にeasy_installを入れるわけですが、はいりません。OpenSSLとの絡みでhashlib.md5が使えないから。easy_installを入れるときにmd5を検証するのに使ってるんですよね。

hashlibをインストール

なんで、自前でhashlibを単体でいれます。

 code
  1. cd ~/root/usr/local/src
  2. wget http://code.krypto.org/python/hashlib/hashlib-20081119.tar.gz
  3. tar zxvf hashlib-20081119.tar.gz
  4. cd hashlib-20081119
  5. vi setup.py
  6.  

はい、setup.pyを編集しましょう。普通にbuildするとこれでもmd5が入りません。

 code
  1. 105   if (ssl_inc_dir and
  2. 106     ssl_lib is not None and
  3. 107     openssl_ver >= 0x00907000):
  4. 108
  5. 109     print 'Using OpenSSL version 0x%08x from' % openssl_ver
  6. 110     print ' Headers:\t', ssl_inc_dir
  7. 111     print ' Library:\t', ssl_lib
  8. 112
  9. 113     # The _hashlib module wraps optimized implementations
  10. 114     # of hash functions from the OpenSSL library.
  11. 115     exts.append( Extension('_hashlib', ['_hashopenssl.c'],
  12. 116                include_dirs = [ ssl_inc_dir ],
  13. 117                library_dirs = [ os.path.dirname(ssl_lib) ],
  14. 118                libraries = osNameLibsMap[os.name]) )
  15. 119   exts.append( Extension('_sha', ['shamodule.c']) )         
  16. 120   exts.append( Extension('_md5',                  
  17. 121           sources = ['md5module.c', 'md5.c'],
  18. 122           depends = ['md5.h']) )
  19.  

119-120あたり、強制的に_md5を入れるようにします。あとは

 code
  1. ~/root/usr/local/bin/python setup.py build
  2. ~/root/usr/local/bin/python setup.py install
  3.  

hashlibが入ります。

easy_installをインストール

これでmd5が使えるようになったので

 code
  1. cd ~/root/usr/local/src
  2. wget http://peak.telecommunity.com/dist/ez_setup.py
  3. ~/root/usr/local/bin/python ez_setup.py
  4.  

これで無事easy_installが入ります。あとは

 code
  1. ~/root/usr/local/bin/easy_install -U -Z MySQL_Python
  2.  

てな感じで必要なモジュールを入れていきましょう。

というわけで

わりと普通にxreaでPythonが使えています。

01.06.10/12am

Python: パターンマッチしてみる

なんか、趣味では最近はC言語ばっかりだったりするわけですが。

さて、関数型言語系をカジった人なら誰しも取り付かれる、モノ、それがパターンマッチ。パターンマッチが使えると、とにかく直感的にコードをかけますよね。

つーわけで、Pythonでパターンマッチを実装してみました。機能的には

  • リスト,タプルに対するパターンマッチ
  • パターン変数への束縛
  • ガード条件
  • 任意のオブジェクトに対するパターンマッチ
  • 部分パターンの束縛(Ocamlのas)

あたりを実装してみました。これだけあれば、かなり便利にコードをかけます。できるだけ、手軽に書けるように工夫してみました。こんな感じです。

変数束縛とガード。getattrでごにょごにょしてるので簡単にかけます。

python code
  1. m = Match([1,2,3])
  2. if m.when([1,2,m.var]) and m.var > 2:
  3.   print m.var
  4. # >> 3
  5.  

こう使えば、Pythonに念願のswitchが!

python code
  1. m = Match(10)
  2. if m(9):
  3.   print 1
  4. elif m(10):
  5.   print 2
  6. else:
  7.   False
  8. # >> 2
  9.  

部分パターンを束縛してみます。[1,2,m.var]全体をallというパターン変数に束縛します。

python code
  1. m = Match([1,2,3])
  2. if m.when([1,2,m.var]) and m.var > 5:
  3.   False
  4. elif m.when(m._as_all([1,2,m.var])):
  5.   print m.all
  6.   print m.var
  7. else:
  8.   raise StandardError("")
  9. # >> [1, 2, 3]
  10. # >> 3
  11.  

任意のオブジェクトにも使えます。いわゆるレコードに対するマッチも簡単にできるということです。

python code
  1. class Test(object):
  2.   def __init__(self, v1, v2):
  3.     self.v1 = v1
  4.     self.v2 = v2
  5.   def __repr__(self):
  6.     return "Test(%s, %s)"%(repr(self.v1), repr(self.v2))
  7. m = Match([1, Test(2, 3)])
  8. if m.when([1, m._class(Test, {"v1":2, "v2": m.v2})]):
  9.   print m.v2
  10. else:
  11.   False
  12. # >> 3
  13.  

オブジェクトに対するパターンマッチは__match__メソッドを定義するとカスタマイズできます。ここらのアイデアはScalaからいただきました。

python code
  1. class Test2(Test):
  2.   def __match__(self):
  3.     return {"value": self.v1 + self.v2}
  4. m = Match([1, 2, Test2(3,4)])
  5. if m.when([1,2, m._class(Test2, {"value": m.var})]):
  6.   m.var
  7. else:
  8.   False
  9. # >> 7
  10.  

結構いい感じな気がします。

ダウンロード

patternmatch.py

実装のお話

ソースコードはこんな感じ。

python code
  1. class Match(object):
  2.   class _var(str): pass
  3.   class _class(object):
  4.     def __init__(self, klass, attrs):
  5.       self.klass= klass
  6.       self.attrs= sorted(attrs.iteritems())
  7.     def match(self, m, obj):
  8.       props = getattr(obj, "__match__", lambda: obj.__dict__)()
  9.       return issubclass(obj.__class__, self.klass) and \
  10.             m.when(self.attrs, sorted(props.iteritems()))
  11.   class _as(object):
  12.     def __init__(self, name, pattern = None):
  13.       self.name = name
  14.       self.pattern = pattern
  15.     def __call__(self, pattern):
  16.       self.pattern = pattern
  17.       return self
  18.  
  19.   def __init__(self, obj):
  20.     self.obj = obj
  21.     self.bind = {}
  22.  
  23.   def __getitem__(self, key):
  24.     if not self.bind.has_key(key):
  25.       if key.startswith("_as_"):
  26.         return self._as(self._var(key[4:]))
  27.       return self._var(key)
  28.     return self.bind[key]
  29.   __getattr__ = __getitem__
  30.   __call__ = lambda self, *a, **k : self.when(*a, **k)
  31.  
  32.   def when(self, pattern, obj = None):
  33.     if not obj: obj = self.obj
  34.     if isinstance(pattern, (self._var, self._class, self._as)):
  35.       if isinstance(obj, (list, tuple)):
  36.         pattern = [pattern]
  37.         obj = [obj]
  38.  
  39.     if not isinstance(obj, (list, tuple)) and \
  40.       not isinstance(pattern, (list, tuple)) :
  41.       obj = [obj]
  42.       pattern = [pattern]
  43.  
  44.     if not isinstance(obj, (list, tuple)) or \
  45.       not isinstance(pattern, (list, tuple)) :
  46.       self.bind = {}
  47.       return False
  48.  
  49.     if len(obj) != len(pattern):
  50.       if not ((pattern[-1].__class__ == self._var) and pattern[-1].startswith("__")):
  51.         self.bind = {}
  52.         return False
  53.  
  54.     for i, (value, pat) in enumerate(zip(obj, pattern)):
  55.       if value == pat:
  56.         continue
  57.       elif pat.__class__ == self._var and pat.startswith("__"):
  58.         self.bind[str(pat)] = obj[i:]
  59.         return True
  60.       elif pat.__class__ == self._var:
  61.         self.bind[str(pat)] = value
  62.       elif pat.__class__ == self._class:
  63.         if not pat.match(self, value):
  64.           self.bind ={}
  65.           return False
  66.       elif pat.__class__ == self._as:
  67.         if not self.when(pat.pattern, value):
  68.           self.bind ={}
  69.           return False
  70.         self.bind[str(pat.name)] = value
  71.       elif isinstance(value, (list, tuple)) and isinstance(pat, (list,tuple)):
  72.         if not self.when(pat, value):
  73.           self.bind = {}
  74.           return False
  75.       else:
  76.         self.bind = {}
  77.         return False
  78.  
  79.     return True
  80.  

まぁわりかしシンプルですね。


今年も終わりが近づいてまいりました。年をとると時間がすぎるのが速いナァ・・・と痛感しております。

12.11.08/01am

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages