Python.use(better) #range:《余録》@spyOn

記事一覧 Python.use(better)《Python3.1》

range:《余録》@spyOn

《著》森こねこ、小粒ちゃん+∞《監修》小泉ひよ子とタマゴ倶楽部
第0版♪2001/03/02 ● 第1版♪2003/05/25 ● 第2版♪2004/06/01 ● 第3版♪2009/02/28

組み込み関数と同等の機能を実現する過程を通して「関数」の理解を深めます。
※ Python1.5 で作成した例題を、Python3.1 で再構成しました。

事例:コードの解説

from time import time

def spyOn(f):
    def g(*args):
        t1 = time(); r = f(*args); t2 = time()
        return r, t2-t1
    return g
    def myrange_args(f):
        ...

    def myrange_keys(f):
        ...

    def myrange0(start=0, stop=None, step=1):
        while start < stop:
            start += step
            yield start

    @myrange_args
    @myrange_keys
    def myrange1(start=0, stop=None, step=1):
        s = []
        while start < stop:
            s.append(start)
            start += step
        return s

    @myrange_args
    @myrange_keys
    def myrange2(start=0, stop=None, step=1):
        while start < stop:
            start += step
            yield start
■ #1: yield 文を利用する:デコレーターを伴わない
    def myrange0(start=0, stop=None, step=1):
        while start < stop:
            ...
            yield start

位置引数だけに頼って、キーワード引数を利用しないと、省略可能な引数が限定されます。

■ #2: リストを利用する:デコレーターを伴う
    @myrange_args
    @myrange_keys
    def myrange1(start=0, stop=None, step=1):
        s = []
        while start < stop:
            ...
        return s

数列を列挙したリストを生成して、関数のリターン値にします。

■ #3: yield 文を利用する:デコレーターを伴う
    @myrange_args
    @myrange_keys
    def myrange2(start=0, stop=None, step=1):
        while start < stop:
            ...
            yield start

yield 文を利用すると、ジェネレーター関数になります。

《Note》トリレンマ:「プログラム vs. プログラミング」どちらの効率を優先するか、そのトレードオフ(デコレーターの導入/ジェネレーターの導入)だけではなく、利用者の便宜(キーワード引数の導入)にも配慮する必要があります。

事例:モジュールを起動する

■ 全項目を確認する

全ステップの「項目」を確認するには、関数 do を利用します。

$ python -i my_range.py
>>> do()
 0: step00 -- def myrange(n):
 1: step01 -- while n:
 2: step02 -- s[0:0] = str(n)
 3: step03 -- def myrange(c, n):
 4: step04x -- def myrange(*args):
 5: step05 -- @myrange_args
 6: step06 -- def myrange(c, n, m):
 7: step07x -- def myrange_args(f):
 8: step08 -- def myrange_keys(f):
 9: step09 -- def g(*args, **keys):
10: step10 -- yield start
11: step_spyOn -- @spyOn
>>>
■ 各項目を実行する

各ステップの「動作」を確認するには、関数 do に実引数を指定します。

>>> do(11)
>>> # -------------------------------------------------- step_spyOn
>>> range(0,1000000,1)
range    (None, 0.07149791717529297)
myrange0 (None, 0.3966100215911865)
myrange1 (None, 0.5240530967712402)
myrange2 (None, 0.3833169937133789)
>>> range(0,10000000,10)
range    (None, 0.0773470401763916)
myrange0 (None, 0.4027891159057617)
myrange1 (None, 0.5115780830383301)
myrange2 (None, 0.37252092361450195)
>>> do(11)
>>> # -------------------------------------------------- step_spyOn
>>> range(0,1000000,1)
range    (None, 0.07951593399047852)
myrange0 (None, 0.38605213165283203)
myrange1 (None, 0.5196981430053711)
myrange2 (None, 0.3934459686279297)
>>> range(0,10000000,10)
range    (None, 0.0772550106048584)
myrange0 (None, 0.4185311794281006)
myrange1 (None, 0.5148370265960693)
myrange2 (None, 0.38182497024536133)
>>> do(11)
>>> # -------------------------------------------------- step_spyOn
>>> range(0,1000000,1)
range    (None, 0.07607197761535645)
myrange0 (None, 0.37715697288513184)
myrange1 (None, 0.547645092010498)
myrange2 (None, 0.3761270046234131)
>>> range(0,10000000,10)
range    (None, 0.07460308074951172)
myrange0 (None, 0.4025721549987793)
myrange1 (None, 0.5118789672851562)
myrange2 (None, 0.3659961223602295)
>>>

1,000,000個の数列を生成させるとともに、それを何度か実行した結果を比較します。

  • yield 文を利用すると、リストと比べて、実行効率が改善されるものの、
  • 組み込み関数 range と比べて、約5倍の処理時間を要するのが分かります。

また、

  • デコレーターを導入しても、その影響はほとんどないことが分かります。

《余録》テストケース

    local = locals()
    cases = "range","myrange0","myrange1","myrange2",
    s2 = "%%-%ds"%max(len(e) for e in cases)

    times = 10**6
    s1 = "%s(0,%d,1)"
    print(">>>",s1%(cases[0],times))

    for s in cases:
        @spyOn
        def ex(n):
            for e in eval(s1%(s,n),local): pass
        print(s2%s,ex(times))

    times = 10**7
    s1 = "%s(0,%d,10)"
    print(">>>",s1%(cases[0],times))

    for s in cases:
        @spyOn
        def ex(n):
            for e in eval(s1%(s,n),local): pass
        print(s2%s,ex(times))

》こちらに移動中です《
TOP


関連記事

Last updated♪2009/11/08