I love epiphanies. :)
For some reason, I haven't been able to figure out "lambda" functions in Python. Granted, I've not put a great deal of time into them. It's just that whenever I would see them in someone else's code, I couldn't immediately figure out what they were doing, so I'd just add another mark next to the "Need to research lambdas" entry in my mental to-do list and move along. To make a long story short, I ran across
a page today that flipped the light switch on lambdas. But that's not the epiphany of which I speak. The epiphany hit when I began trying to figure out what (if anything) I could do with those lovely little lambdas in my Python programming at work.
Most of my development work involves the creation of tools with some kind of graphical user interface (GUI). When assigning a command to a Maya GUI element in Python, it expects either a string that contains some Python code to execute, or a pointer to a function or method that will be called. In the vast majority of situations, I'll use the latter option. If I don't need to pass any data to the target function, there's no problem, and Maya gets the function pointer as expected:
import maya.cmds as mc
def blah(*args):
# "*args" is required because even though the
# command doesn't pass any data, Maya passes
# some anyway. Go figure...
print "You touched me!"
mc.window(w=500, h=500)
mc.columnLayout()
mc.button(l="Touch!", c=blah)
mc.showWindow()
However, in most of the GUIs that I create, some control will need to pass specific data to the function that it calls. The problem is that once you include parentheses to pass data to the function, Maya is no longer getting a function pointer. It's getting the value (if any) returned by the called function, or
None
if the function doesn't return anything.
Up until now, I've been using nested functions to get around this problem. By defining and returning a "dummy" function inside the main function that is called by the GUI element, Maya will get the function pointer it wants, and I can pass in any data that I please:
import maya.cmds as mc
def blah(value):
def b(*args):
# "*args" is still required because this is the
# function that Maya will ultimately call when
# the button is pushed
print "I was given:", value
return b
mc.window(w=500, h=500)
mc.columnLayout()
mc.button(l="Touch!", c=blah(10))
mc.showWindow()
This process has been working fine, but in the back of my mind, I kept hoping to find a more elegant solution.
Enter my new friend: the lambda!
After some experimentation, I learned two very helpful things about lambdas. The first is that the expression evaluated by a lambda doesn't necessarily have to have any connection to the data it is passed. For example, a "normal" lambda definition might look something like this:
x = lambda y: y * 2
In this case, calling
x(5)
will return
10
(the 5 gets passed to
y
, when is then evaluated through the expression
y*2
to yield
10
, which is then returned). However, the expression can be changed to return something that has nothing to do with the value passed in through
y
, like so:
x = lambda y: 15
In this case, no matter what value you pass,
15
will always be returned.
Based on this example alone, I can already begin to use lambdas to simplify my example code above. (It's simpler on the function definition side of things because we get rid of the nesting issue, but some might see the syntax of assigning the desired function call to the GUI element a tad more confusing.)
import maya.cmds as mc
def blah(value):
print "I was given:", value
mc.window(w=500, h=500)
mc.columnLayout()
mc.button(l="Touch!", c=lambda x:blah(10))
mc.showWindow()
What happens is that the mystery-data passed by Maya gets assigned to
x
in the lambda definition, but we don't need to use it. All we need is to call our function with the desired value.
For some GUI elements, though, the data passed by Maya is actually useful. Take an intSlider, for example:
import maya.cmds as mc
def blah(value):
print "The slider value is", value
mc.window(w=500, h=500)
mc.columnLayout()
mc.intSlider(dragCommand=lambda x:blah(int(x)))
mc.showWindow()
In this situation, the data passed by Maya when dragging the slider is the slider's value. However, it's passed as a Unicode string, so I just converted it to an integer before passing it to the function. This means I don't have to query the slider, as the data I need has already been passed.
Some GUI operations, like dragging and dropping, pass more than one argument to the target function. No matter...just provide the requisite number of arguments in the lambda definition (i.e.
lambda w,x,y,z: ....
) and pass them along to the target function as desired. Or, as in one particular case where I wanted to substitute my own data in place of what the drag operation passed, you can take advantage of the other nifty thing I learned through my experiments: a lambda can accept arbitrary argument lists, just like normal functions.
# other GUI code here
mc.button(l="blah, dragCallback=lambda *x:boo(myData))
That's all for now. Happy Python GUI building!