lambda!!


<< WARNING!! >>
今回の話は、ちょっと込み入った話になりますが、
一部僕が誤解して覚えている箇所があるかもしれませんので、
全体的に疑ってお読みください。
特にリファレンスとかポインタとかスコープとかの話に及んでいるあたりは、
かなり怪しいので、そこんとこ、どうぞよろしく。

例えば、MayaでGUIを組むとして、
モード切り替え用にボタンを3つ用意したとします。


何のモード切り替え、とか言うところは置いといて、モード切り替え。

で、例えばMELで書くならば、こんな感じになると思います。

button -l "Mode 1" -c "changeMode(1)";
button -l "Mode 2" -c "changeMode(2)";
button -l "Mode 3" -c "changeMode(3)";

この時changeModeはglobal procになっている必要がありますが、
単純に文字列を与えることで呼び出せます。で、引数も文字列として付加できます。

これがおそらくMayaのGUIによる一般的な関数呼び出しだと思います。

ところがPythonだと、関数のリファレンスを渡すことが出来ます。

cmds.button( l="Mode 1", c=changeMode )

個人的には、プログラミングやってます的な感じがして、非常に好きな書き方ですwww
Cで言うところのポインタ渡しとか参照渡しみたいな機能です。
つーかこれはJavascriptでもやれるので、MELが出来ないってことのほうが驚きなんです。多分。
MELの貧弱さは、本格的なプログラマが見たら、ブーイングものなんじゃないでしょうか。
MELから入った貧弱者なので、よくわかりませんが。

しかし参照渡しに問題が無いわけではなくて、この場合、数を増やすのが非常に困難です。
参照を渡すと言うことは、関数の情報が格納されているポインタを渡すということです。
つまり、関数そのものを渡しているわけで、そこに情報を付加して渡すと言うことが出来ないのです。

cmds.button( l="Mode 1", c=changeMode(1) )

このスクリプトはエラーになります。
というか見ればまぁ分かりますよね、、これだと関数が実行された結果が渡されてしまいます。
buttonの-cオプションは、関数か文字列しか取れないため、コレはエラーになるのです。

なので、単純に数を増やそうと思ったら、こうする必要があります。

cmds.button( l="Mode 1", c=changeMode1 )
cmds.button( l="Mode 2", c=changeMode2 )
cmds.button( l="Mode 3", c=changeMode3 )

これは関数を何個も書く必要があって、なんともまどろっこしいです。
先ほども言った通りで、Pythonでも文字列を使って呼ぶことは出来ます。
でもなぁ、、、みたいな部分はありまして。。

例えばツールごとにクラスを作っていたとします。
そうした場合、self.changeModeみたいに関数を呼びたいわけです。
その方がOOPっぽいし、名前がかぶるとかかぶらないとか、いろんな問題を気にしなくて良いわけです。

文字列で呼ぶ場合、イメージ的には、
「家の中にいる家族を、一旦外に出て大声で呼ぶ」
というような行為に相当するのではないでしょうか。

おとーーーーーさーーーーーーーーーーーん

・・・。

なんともマヌケです。

家の中にいるんなら、家の中から呼びたい。
でも関数を無駄にたくさん作るのはメンテナンス性に欠ける上に、美しくない。

さて。

ここでlambda(ラムダ)を使います。
lambdaはPythonが関数型言語(主にHaskell?)に影響を受けて作られた機能だそうで、
無名関数を作成することが出来ます。

lambdaに関して、詳しくは個々で調べてもらうとして、
とりあえず今回こんな風に使ってみました。

cmds.button( l="Mode 1", c=lambda *args : changeMode(1) )
cmds.button( l="Mode 2", c=lambda *args : changeMode(2) )
cmds.button( l="Mode 3", c=lambda *args : changeMode(3) )

これがすごく美しい形かと言われれば、謎です。
本格派のPythonistaからは怒られてしまうかもしれません。
ですが、僕が試した中では一番しっくり来ました。

無名関数とは言え、何個も関数作ってるなら同じじゃん、と言われればそうかもしれません。
が、個人的は、こっちの方が分かりやすくて、引数も渡せて、記述も楽で、
よりPythonとOOPを活用している感が強いと思うんですが、どうですか。

もしよりエレガントな方法をご存知の方がいらっしゃったら是非お教えいただきたいです。

ちなみに、lambdaに引数として渡している*argsは、不定個の引数を得るためのもので、
*名前という書き方をすることにより、そういうことが出来る模様です。
また、**名前とすると、不定個の辞書が取れるっぽいです。
その辺、詳しくはまた個々で。。
・・・丸投げですみません。

で、MayaのUIのコマンド実行系のオプション(cとかccとかoncとか)に関数のリファレンスを渡すと、
どうやらu”(つまり空白のユニコード文字列)がくっついて実行されるようでした。

なので、

def cmd():
  pass

という関数をUIのアクションから実行させようとすると、エラーになります。

def cmd(*args):
  pass

としてあげると、多分通ります。
まぁpassしてるので何も起きませんがwww

とりあえず、lambdaを使うことでUIからの関数呼び出しに関して
気になっていた点が解消された気がします。
関数呼び出す関数なんて多分メモリ問題とかも気にしなくていいんじゃないかと思います。
何百個何千個作るわけじゃないのでたかが知れてるし。

あ、textScrollListとかにループで突っ込んでいったらさすがにメモリ問題気になるかもしれませんが。
まぁそれにしてもなんとかなるんじゃまいか。
多分! ←希望的観測

なんとなく、すっきりしました。

「lambda!!」への4件のフィードバック

  1. ちょっと面倒くさいですがクロージャを使う方法もあります。

    def createChangeModeFunc(value):
    def valuedChangeMode():
    …valueを使う…
    return valuedChangeMode

    cmds.button( l=”Mode 1″, c=createChangeModeFunc(1) )
    cmds.button( l=”Mode 2″, c=createChangeModeFunc(2) )
    cmds.button( l=”Mode 3″, c=createChangeModeFunc(3) )

  2. >hohehohe2さん
    どうも、ご無沙汰しております。

    なるほど!こんな方法が!!
    クロージャというものなんですね。
    これは参考になります!
    書き方もlambdaが入ったときみたいにちょっとごっちゃりしたりしてないですし。

    アドバイスありがとうございます!!

  3. 通りすがりです。
    2.5以降だと、標準ライブラリ内のfunctools.partial が使えますよ。

    import functools
    functools.partial(changeMode, 1)

    # GUI部品の初期化などで、同じ引数を何度も書く場合などで、
    # 簡易macro代わりに、よく使います。>lambda. MELは知らないけど、こんな感じ。
    create_mode_button = lambda val: cmds.button(l=’Mode %d’ % val,c=functools.partial(changeMode, val)
    create_mode_button(1)
    create_mode_button(2)
    create_mode_button(3)
    del create_mode_button

  4. >tさん
    はじめまして。コメントありがとうございます。

    >functools.partial
    おおおおおおこんなものが!
    なるほど、確かに場合によってはかなり効果的な感じがします。
    実際にこういうのが効きそうなスクリプト書いたこともありますし。

    これはナイス情報です。
    ありがとうございます!

コメントを残す

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