静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念


【python】キーワード引数と可変長キーワード引数(kwargs)の競合によるエラー

はじめに

 既存の関数の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の引数周りは意外と面倒くさいんです。柔軟といえば柔軟ですが、破綻すれすれな感じもする。