DirectButtons extraArgs relies on a __name__ argument
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
Panda3D |
Fix Released
|
Undecided
|
David Rose |
Bug Description
I was first trying to use this code:
)
But for an unknown reason, using lambda was the source of a very strange behaviour: whatver button I was clicking, the command was invoked with the last 'key' argument. Like if I had clicked on the last button!
sjoerd_ on IRC told me to use functools.partial instead of lambda. Wich caused this exception:
Traceback (most recent call last):
File "main.py", line 1038, in <module>
run()
File "/usr/share/
self.
File "/usr/share/
self.step()
File "/usr/share/
self.mgr.poll()
File "/usr/share/
self.doEvents()
File "/usr/share/
processFunc
File "/usr/share/
messenger.
File "/usr/share/
self.
File "/usr/share/
method (*(extraArgs + sentArgs))
File "/usr/share/
apply(
File "/home/
Func(command),
File "/usr/share/
FunctionInt
File "/usr/share/
name = self.makeUnique
File "/usr/share/
name = 'Func-%s-%d' % (func.__name__, FunctionInterva
AttributeError: 'functools.partial' object has no attribute '__name__'
So I added a __name__ argument to my functools.partial:
)
And it fixed my strange bug.
The ideal would be to not rely on this __name__ argument to fix functools compatibility, and find why lambda caused this strange bug.
Changed in panda3d: | |
status: | New → Fix Committed |
assignee: | nobody → David Rose (droklaunchpad) |
Changed in panda3d: | |
status: | Fix Committed → Fix Released |
Unlike some other languages, Python's lambda syntax doesn't perform a full closure on its parameters automatically. This means that when you use a syntax like "lambda: func(key)", the variable reference "key" does not retain the value of the variable at the time you bound it, but rather looks up the variable "key" from the *calling* namespace. This is almost never what you intended. To force Python to bind in a variable reference, you pass it in as a default parameter, like this: "lambda key = key: func(key)". This is a bit of a hairy kludge, but it's the Python way.
You can also use use Panda's Functor class to do this for you. "from direct. showbase. PythonUtil import Functor", then pass "Functor(func, key)", instead of a lambda object, to the command parameter.
I've never seen functools.partial before, but it apparently is meant to do the same thing as our Functor class. Unfortunately, it doesn't define a __name__ member for some reason (unlike almost every other kind of Python object), so it causes a crash in the FunctionInterval code which assumes the function object you pass does have a __name__ member. I've just committed a workaround to FunctionInterval to allow it to work without this.