commonly accessible instance attributes in function (CAIAF) テクニック

追記(2007/8/2):
namespace function object (NFO) アプローチに改名。


今月25日に使ったテクニックに名前が付いていないので適当に付けた。どこかに付けている人がいそうだが、とりあえず自分の記憶用にメモ。クロージャと組み合わせると便利かも。もし、適切な名前があったり、既にドキュメント化されていたり、使ってみて問題ある場合は教えて欲しい。


テクニックとしては、関数の内部でダミーの関数を作成し、それに属性を関連付けるというもの。以下の2つの機能が実現可能。

  1. ネストした名前空間を越えて属性がアクセス可能
  2. 関数の外側に関数内部の属性を公開可能
# 1.
def foo(i):
    def f():pass  # ダミーで関数 f を作成 (foo呼び出しごとにインスタンスが作成される)
    f.n = i       # f に変数を関連付ける
    def cnt():
        f.n += 1    # もちろん、値の変更可能
        return f.n  # ネストした名前空間でアクセス可能
    return cnt

counter = foo(0)
print counter()  # 1
print counter()  # 2

# 2.
def foo():
    def f():pass
    def bar(): return 'bar()'
    f.x = 1
    f.bar = bar  # 関数を保持することも可能
    return f     # f を返値とすることで、外に公開

f = foo()
print f.x      # 1
print f.bar()  # 'bar()'

str.startswith が遅い件

デコレータに対応するようにして、時間計測。

import time

def profile(func):
    def foo(*a, **k):
        t = time.time()
        r = func(*a, **k)
        print '%s: %g sec elapsed' % (func.__name__, time.time() - t)
        return r
    return foo

if __name__ == '__main__':
    @profile
    def find(s1, s2):
        for i in xrange(100000):
            str.find(s1, s2)
            
    @profile
    def startswith(s1, s2):
        for i in xrange(100000):
            str.startswith(s1, s2)
                
    @profile
    def op_eql(s1, s2):
        for i in xrange(100000):
            s1[:5] == s2
    
    find('func_name', 'test_')
    startswith('func_name', 'test_')
    op_eql('func_name', 'test_')

    # find: 0.0939999 sec elapsed
    # startswith: 0.0780001 sec elapsed
    # op_eql: 0.016 sec elapsed

実行するたびに若干値は違うが、傾向的にはこんな感じ。文字列の比較する長さによるのかもしれないが、== を使った方法が断然早い。== が成立するときも早い。但し、バージョンが上がったときに順序が逆転する可能性もある。パフォーマンステストは難しいので、とりあえずここまでにしておくつもり。