Pyside dynamically creating widgets and attaching signal isse

414 views
Skip to first unread message

Rudi Hammad

unread,
Nov 14, 2021, 1:15:17 PM11/14/21
to Python Programming for Autodesk Maya
Hello,

I wrote this simple ui to ilustrate the issue. As the title says I am creating dynamically
some widgets and attaching a signal. In this case 4 buttons that when clicked, print their name.
The problem if you run the code is that all buttons are printing the same letter 'D', which is the last signal to be set.


Any ideas how to create dynamic signals that will be attached to the corresponding widget?

thanks

R

Justin Israel

unread,
Nov 14, 2021, 2:21:29 PM11/14/21
to python_in...@googlegroups.com
Hey there. So this is caused by the fact that a python lambda does not perform a full closure over the scoped variable, which leads to bugs like this when the locally scope variable (n) is changing over the course of the loop. Each lambda you are passing as a slot function has a reference to that scope, but will evaluate the last value of the variable n.

(note: dont do the following)
The safer way to express the lambda that properly captures the variable value at that time is something like this double-lambda:

fn = lambda val: lambda: self.fooPrint(n)
btn.clicked.connect(fn(n))

This directly captures the current value of your loop variable and returns a new lambda with a closure over that value.

Because this is such a fragile mechanism, PEP advises against using anonymous lambdas in the first place:
https://www.flake8rules.com/rules/E731.html

So what you could do instead is wrap local def functions (there are multiple ways to do this, but here is one way):

for n in 'ABCD':
    def wrap(val=n, *args):
        self.fooPrint(val)
    btn.clicked.connect(wrap)

And this is pretty much what functools.partial() is for:

from functools import partial

for n in 'ABCD':
    btn.clicked.connect(partial(self.fooPrint, n))



thanks

R

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/eeb1960d-1e46-43d0-a0d9-1107f695a859n%40googlegroups.com.

Rudi Hammad

unread,
Nov 14, 2021, 2:44:36 PM11/14/21
to Python Programming for Autodesk Maya
Amazing, thanks for the explanation.
I've used wrappers before in other contexts but not partial.  Didn't found that issue until now.
Nothing much to add! that solved it. Thank you

Justin Israel

unread,
Nov 14, 2021, 2:55:40 PM11/14/21
to python_in...@googlegroups.com
On Mon, Nov 15, 2021 at 8:44 AM Rudi Hammad <rudih...@gmail.com> wrote:
Amazing, thanks for the explanation.
I've used wrappers before in other contexts but not partial.  Didn't found that issue until now.
Nothing much to add! that solved it. Thank you

Welcome!

Also, I should point out that I have seen cases in either pyqt or pyside where using a partial() or local scope function wrapper could lead to a memory leak on the object with the signal, depending on how objects are parented and deleted. This can happen because python (I think) won't be able to automatically disconnect the signal if it doesnt see that the object owning the slot has been garbage collected. When you use a partial() or a local wrapper, its a branch new object. So switching to always using methods bound to your object seems to work better (self._printFooHandler(self.printFoo, n))
Not saying it happens all the time, but I have seen it before.
 

Rudi Hammad

unread,
Nov 14, 2021, 3:40:51 PM11/14/21
to Python Programming for Autodesk Maya
Noted

luceric

unread,
Dec 1, 2021, 5:42:26 PM12/1/21
to Python Programming for Autodesk Maya
Just FYI, you don't need to use lambdas. In your fooPrint signal handler, you can call self.sender() to get the button that's calling. It's a method of the base class, QObject


Reply all
Reply to author
Forward
0 new messages