Python.use(better) #range: step09 -- def g(*args, **keys):

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

range: 09 -- def g(*args, **keys):

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

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

事例:コードの解説

リファクタリングを実践すると、特定の変数に依存しない「抽象表現」が可能になります。

    def myrange_args(f):
        def g(*args, **keys):            
            if args:
                args = (
                    (0, args[0], 1),    #1
                    list(args)+[1],     #2
                    args,               #3
                    )[len(args)-1]
            else:
                args = 0, 0, 1
            return f(*args, **keys)
        return g

    def myrange_keys(f):
        def g(*args, **keys):
            ks = "start","stop","step"
            kw = {k:v for k,v in zip(ks, args)}
            kw.update(keys)
            return f(**kw)
        return g

    @myrange_args
    @myrange_keys
    def myrange(start=0, stop=None, step=1):
        s = []
        while start < stop:
            s.append(start)
            start += step
        return s
■ #1: シーケンスを利用する
        def g(*args, **keys):            
            if args:
                args = (
                    (0, args[0], 1),    #1
                    list(args)+[1],     #2
                    args,               #3
                    )[len(args)-1]
            else:
                args = 0, 0, 1
            return f(*args, **keys)

前ステップ 》では、可変長の引数 args をもとに、各変数 c,n,m を介して、関数呼び出し f() の位置引数 c,n,m を確定させました。ここでは、シーケンス args を利用して、特定の変数に依存しないコードを記述します。

《Tips》シーケンスを利用する:特定の変数に依存しない、簡潔で見通しの良いコードを記述できるだけでなく、条件分岐 if/elif/else に象徴される「ハードコーディング」に代えて、柔軟な対処を可能にする OOP への布石にもなります。

■ #2: キーワード引数
        def g(*args, **keys):
            ks = "start","stop","step"
            kw = {k:v for k,v in zip(ks, args)}
            kw.update(keys)
            return f(**kw)

前ステップ 》では、固定長の位置引数 c,n,m をもとに、関数呼び出し f() のキーワード引数 start=,stop=,step= を確定させました。ここでは、辞書 kw を利用して、特定の位置引数に依存しないコードを記述します。

《Tips》シーケンスを利用する:特定の変数に依存しない、簡潔で見通しの良いコードを記述できるだけでなく、条件分岐 if に象徴される「ハードコーディング」に代えて、柔軟な対処を可能にする OOP への布石にもなります。

■ #3: デコレーター
    @myrange_args
    @myrange_keys
    def myrange(start=0, stop=None, step=1):
        ...

@ に続く関数定義の記述は、以下と同等です。

    myrange = myrange_args(myrange_keys(myrange))

まず、myrange_args, myrange_keys を適用した後で、myrange を実行します。関数 myrange の本体にある、既存のコードの断片は不変です。 違いは、その前に @myrange_args, @myrange_keys を追記するだけです。

  • myrange_args, myrange_keys では、myrange に影響を与えず、その機能を自由に拡張できます。
  • myrange は、myrange_args, myrange_keys から影響を受けないので、そのコードは不変です。

《Tips》開放閉鎖原則@ で始まるデコレーター構文を利用すると、開放閉鎖原則に沿って、簡潔で見通しの良いコードを記述できるだけでなく、要求仕様の変更にも柔軟に対処できます。

《Note》デコレーター構文の導入:この課題を作成したとき(Python1.5)はまだ、デコレーター構文はありません(Python2.4 以降)が、その本質は変わりません。
※ Release 2.4.4, documentation updated on 18 October 2006.

《発展課題》第3引数に負数を指定しても、

>>> s = range(10,0,-2); list(s)
[10, 8, 6, 4, 2]
>>> s = myrange(start=10,stop=0,step=-2); list(s)
[]

組み込み関数 range と同じ結果が得られるように、myrange を改訂してください。

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

■ 全項目を確認する

全ステップの「項目」を確認するには、関数 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(8)
>>> # -------------------------------------------------- step09
>>> for e in range(6):
    s = range(e+-1,4,2)
    print(s,list(s))
range(-1, 4, 2) [-1, 1, 3]
range(0, 4, 2) [0, 2]
range(1, 4, 2) [1, 3]
range(2, 4, 2) [2]
range(3, 4, 2) [3]
range(4, 4, 2) 
>>> for e in range(6):
    s = myrange(e+-1,4,2)
    print("myrange(%d,4,2)"%(e+-1), s)
myrange(-1,4,2) [-1, 1, 3]
myrange(0,4,2) [0, 2]
myrange(1,4,2) [1, 3]
myrange(2,4,2) [2]
myrange(3,4,2) [3]
myrange(4,4,2) 
>>> for e in range(6):
    s = range(e+-1,4)
    print(s,list(s))
range(-1, 4) [-1, 0, 1, 2, 3]
range(0, 4) [0, 1, 2, 3]
range(1, 4) [1, 2, 3]
range(2, 4) [2, 3]
range(3, 4) [3]
range(4, 4) 
>>> for e in range(6):
    s = myrange(e+-1,4)
    print("myrange(%d,4)"%(e-1), s)
myrange(-1,4) [-1, 0, 1, 2, 3]
myrange(0,4) [0, 1, 2, 3]
myrange(1,4) [1, 2, 3]
myrange(2,4) [2, 3]
myrange(3,4) [3]
myrange(4,4) 
>>> for e in range(5):
    s = range(e)
    print(s,list(s))
range(0, 0) 
range(0, 1) [0]
range(0, 2) [0, 1]
range(0, 3) [0, 1, 2]
range(0, 4) [0, 1, 2, 3]
>>> for e in range(5):
    s = myrange(e)
    print("myrange(%d)"%e, s)
myrange(0) 
myrange(1) [0]
myrange(2) [0, 1]
myrange(3) [0, 1, 2]
myrange(4) [0, 1, 2, 3]
>>> for e in range(6):
    s = myrange(start=e+-1,stop=4,step=2)
    print("myrange(start=%d,stop=4,step=2)"%(e+-1), s)
myrange(start=-1,stop=4,step=2) [-1, 1, 3]
myrange(start=0,stop=4,step=2) [0, 2]
myrange(start=1,stop=4,step=2) [1, 3]
myrange(start=2,stop=4,step=2) [2]
myrange(start=3,stop=4,step=2) [3]
myrange(start=4,stop=4,step=2) []
>>> s = myrange(6,step=2); s
[0, 2, 4]
>>> s = myrange(6,start=3); s
[3, 4, 5]
>>> s = myrange(6,start=-1,step=2); s
[-1, 1, 3, 5]
>>> s = myrange(start=-1,step=2,stop=6); s
[-1, 1, 3, 5]
>>> s = myrange(start=-1,stop=3); s
[-1, 0, 1, 2]
>>> s = myrange(step=2,stop=6); s
[0, 2, 4]
>>>

新たに作成した関数 myrange が、組み込み関数 range に準拠していることを確認します。

  • 引数で指定した範囲の等差数列を列挙した、リストが得られます。

さらに、組み込み関数 range と違って、関数 myrange では、キーワード引数を利用できます。

《余録》テストケース

def ex_range4(local, start, end, step):
    ex_range3(local, start, end, step)
    n = end - start + 1

    X = '''for e in range({0}):
    s = myrange(start=e+{1},stop={2},step={3})
    print("myrange(start=%d,stop={2},step={3})"%(e+{1}), s)'''.format(n, start, end, step)
    print_(X, local, "exec")

    source = '''
s = myrange(6,step=2); s
s = myrange(6,start=3); s
s = myrange(6,start=-1,step=2); s
s = myrange(start=-1,step=2,stop=6); s
s = myrange(start=-1,stop=3); s
s = myrange(step=2,stop=6); s
'''.split("\n")

    for e in source[1:-1]:
        print_(e, local)

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


関連記事

Last updated♪2009/11/08