[Python] 可変長引数あれこれ

Pythonでは可変長の引数はこうやって受けます

def hoge(*args, **kwargs):
    pass


argsというlist tuple型の変数に、キーワード無しの引数が、kwargsというdict型の変数にキーワード付きの引数が入ります。
※*argsはlist型だと思ってました、表示見たら違いますね。サーセン。直しときました。

名前は大体args, kwargs、って感じみたいです。他の名前でもOKだと思いますが。

実際にやってみると、こうなります。

>>> def hoge(*args, **kwargs):
...     print args
...     print kwargs
...
>>> hoge(1, 2, 3, ['a', 'b', 'c'], name='my_name', data='100')
(1, 2, 3, ['a', 'b', 'c'])
{'data': '100', 'name': 'my_name'}

キーワード付き引数はキーワード無し引数の前に書け!!ってエラーで何度か怒られた、って記憶をお持ちの方も多いと思いますが、これとも関係あるのかもと思います。

では突然ですが問題です。

>>> def hoge(*args, **kwargs):
...     foo(args, kwargs)
...
>>> def foo(*args, **kwargs):
...     print args
...     print kwargs
...

こんな感じで関数hogeとfooが宣言されていたとします。
この場合、以下を実行したら、どうなるでしょうか。

>>> hoge(1, 2, 3, ['a', 'b', 'c'], name='my_name', data='100')

結果はこうなります。

>>> hoge(1, 2, 3, ['a', 'b', 'c'], name='my_name', data='100')
((1, 2, 3, ['a', 'b', 'c']), {'data': '100', 'name': 'my_name'})
{}

でーん。

あれ?って思いましたか。
そりゃそうだろ、って思いましたか。

僕はこういうのやる時って、そもそもどうやるんだ?と思ってるだけ思ってて、実際にここで引っかかったことはありませんでした。
というかもっと古典的に必要な引数を全部宣言してそのまま渡してましたwwwwwww
うぇwwwwwwwwwwwwwwwwwwww

これが困るのは、例えばクラスの__init__なんかですかね。
何かのクラスを継承していて、でも親クラスのコンストラクタを呼びたい場合。
でも親クラスでは*args, **kwargsになっている!どうすれば!!

そういう場合はこうします。

>>> def hoge(*args, **kwargs):
...     foo(*args, **kwargs)
...
>>> def foo(*args, **kwargs):
...     print args
...     print kwargs
...
>>> hoge(1, 2, 3, ['a', 'b', 'c'], name='my_name', data='100')
(1, 2, 3, ['a', 'b', 'c'])
{'data': '100', 'name': 'my_name'}

はい期待通りっ。

つまり*と**は、どうなってんのかわかりませんが、
可変長引数を取るだけではなく、list tupleとdictの展開も行えるようになっているようです。
詳しく内部でどうなってるのか知ってる方がいたら逆に教えて欲しいです。

僕はRubyのスクリプトかなんかで、この記述をたまたま見かけて(そもそもRuby自体たまたま目にして)、なんとなく気になって見てみたらこういうことだった、ってことみたいでした。

で、今回これに関して書いたのは、Mayaでスクリプトを書いている際に実際に活躍したからです。
どういう状況だったかというと、
・checkBoxGrpのchangeCommand(-cc)に、それぞれ別の関数を与えたい。
・ただしcheckBoxGrpでは-cc1, -cc2, -cc3…のように、数字を伴う引数を必要とする。
・つまりこういう感じで実行したい

cmds.checkBoxGrp(ctlname, e=True, 'cc%d' % index = func)

わかりづらいですが、cc1とハードコードするのではなく、ループの中でダイナミックに1とか2とかをいれてやりたかったのです。

でも、これは多分OUTなはず。(実際には試してないのでわからない)
実際出来たとしても見た目に美しくないから却下!!!(ぇー

で、どうやったかといいますと、ここで**kwargsなのです。

kwargs = {}
kwargs['e'] = True
kwargs['cc%d' % index] = func
cmds.checkBoxGrp(ctlname, **kwagrs)

これです。

これであとはfuncをループ内で適宜設定してやれば良いと言う訳です。

で、これによっていろいろと物事が嬉しい感じで進んだので、書いてみました。

長々失礼しました&ややこしくてすみません&このブログデザインだとコードが強調表示されないとかでちょっと見づらいですね。

近日中にデザイン変更検討中です。
ソースコードが見やすいやつがいいです。
ちょい調べときます。

長々お付き合いいただいてありがとうございました:P

「[Python] 可変長引数あれこれ」への4件のフィードバック

  1. ディクショナリをそのままキーワード引数にできちゃうわけですね。なんとなく便利そうですね。ただ、引数に何が入ってるかわかりにくくなるので、コメントとかでちゃんとフォローしないと駄目そうですね。

  2. >inagakiさん
    > ディクショナリをそのままキーワード引数にできちゃうわけですね。なんとなく便利そうですね。
    お察しの通り、今回は何となく便利でしたwwwwww

    > ただ、引数に何が入ってるかわかりにくくなるので、コメントとかでちゃんとフォローしないと駄目そうですね。
    そうですね。
    その後の処理で受け取るキーワードが決まってるとかならまぁ適当にながしちゃってもいいと思うんですが(意味のないパラメータを許せるなら:P)、プログラム的に読み易くはないと思うし、おそらく大体自分の美意識に引っかかると思うのでwww、その辺はきっちりしといた方がいいのかも、と思います。

    関数デコレータ(使ったことはないですが)なんかをうまく使おうと思ったら結構便利なのでは?なんて思ったりします。

  3. なるほどねー・・・
    気がつきませんでした。

    >> def f(a=0, b=1, c=2):
    … print a, b, c

    >>> f(**{“b”:100})
    0 100 2

    こんなことができるんですね。
    うん、便利だ。

  4. >hohehohe2さん
    使いどころはいろいろあるのかな、と。
    パラメータをゴリゴリ渡すよりも見た目もスッキリしますし。

コメントを残す

メールアドレスが公開されることはありません。