はじめに
既存の関数のwrapperを作るときなど、可変長キーワード引数を使いたいときがあります。
これは通常のキーワード引数と併用できますが、稀に問題になることがあります。
関数定義のとき
定義するときは割と単純で、問題も少ないです。
以下のような例を考えます。
>>> def f(a, b=0, **kwargs): ... print(a, b, kwargs) ...
a, bをキーワード引数として渡した場合、明示的に定義されている引数が吸い取ります。kwargsには与えられることはありません。
>>> f(a=10, b=20, c=30) 10 20 {'c': 30}
逆に言うと明示的に定義されていなければキーワード引数として渡された引数はkwargsに入るので、この声質をうまく利用すると関数のwrapperが書きやすくなるときがあります。
アンパックで呼び出すとき
辞書のアンパック展開によって引数を渡す場合、いささか問題を含んでいます。
端的に言うと、辞書のキーと他の引数とが名前が被るとエラーになります。
>>> kwargs = {"a":20, "c":100} >>> f(a=10, **kwargs) # aの値として優先したいのは10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() got multiple values for keyword argument 'a'
こういう事態を避けるのは一見簡単そうな気もしますが、
- 任意のkwargs(どこかからやってきた)でとりあえず無事に渡せるようにしておきたい
- 引数aは(今この場所で)値を指定して使いたい
というようなときは意外と困難があります。
こういうときは、kwargsを渡さない訳にはいかないですから、aを指定するのをやめてあげます。代わりにkwargsの中に新しくaを入れます。
>>> kwargs = {"a":20, "c":100} >>> kwargs_copy = kwargs.copy() # 変更するのでコピーに対して操作する >>> kwargs_copy["a"] = 10 >>> f(**kwargs_copy) 10 0 {'c': 100}
たぶんこれでいいでしょう。
まとめ
ということで、Pythonの引数周りは意外と面倒くさいんです。柔軟といえば柔軟ですが、破綻すれすれな感じもする。