Python bite: 引数丸投げ
Tagged:

Python では、 関数定義の引数記述に "*" や "**" を使用することで、キーワード引数や任意引数リストを簡単に使用することができます。例えば:

>>> def func(a, *list):
...     print list
...
>>> func(1, 2, 3, 4)
(2, 3, 4)
任意引数リストへの自動的な束縛

ここで Decorator パターン的な実装が必要になった場合、 普通に=何も考えないで実装するなら、 以下のようになるのではないでしょうか?

def wrappee(a, *list, **dic):
    print 'wrappee'
    print list
    print dic

def wrapper(a, *list, **dic):
    print 'wrapper'
    print list
    print dic
    wrappee(a, list, dic)
『丸投げ』な Decorator パターン

何も考えて無いだけあって、 この実装は咬まれます

上記関数定義で、 wrapper(1, 2, 3, 4, x=5, y=6, z=7) を評価してみましょう。

wrapper
(2, 3, 4)
{'y': 6, 'x': 5, 'z': 7}
wrappee
((2, 3, 4), {'y': 6, 'x': 5, 'z': 7})
{} ※ dic は空
咬まれました

全く思った通りになりません。

wrapper() における wrappee() 呼び出しを、 以下のように変えてみましょう。

    wrappee(a, list=list, dic=dic)
名前を指定して束縛してみる

キーワード引数の束縛仕様を考えれば、 薄々駄目な予感はしていますが、一応動かしてみましょう (wrapper 出力は省略)。

wrappee
() ※ list は空っぽ
{'list': (2, 3, 4), 'dic': {'y': 6, 'x': 5, 'z': 7}}
更に悪化

予感通り、やっぱり駄目です。 キーワード引数扱いになるのですから、 辞書が入れ子になってしまうのは当然ですね。

しかし、そこはそれ、 長年の勘が『以下のように直してみ?』と告げるので、 直して実行してみたら、 思ったように動いてくれました。

    wrappee(a, *list, **dic)
任意引数リスト/キーワード引数を直接束縛

実行してみると:

wrappee
(2, 3, 4)
{'y': 6, 'x': 5, 'z': 7}
期待通りの結果

当初、先に参照した『Python チュートリアル』にも書かれていないと思っていたのですが、 同僚から「4.7.4 引数リストのアンパック で触れている」 との指摘を受けました。

こんな間近に書いてあったのに見落としていたとは…