[PYTHON] About PyQt signal, connect and lambda expressions

-Conclusion -Introduction -[lambda expression](#lambda expression) -[Difference between def statement and lambda expression](Difference between #def statement and lambda expression) --[def and lamnda as arguments of connect](def and lamnda as arguments of #connect) ―― [When is the def function useless? ](When is the #def function useless?) -[Use a lambda expression for connect](Use a lambda expression for #connect) --[Why you shouldn't pass the def function to connect](Why you shouldn't pass the def function to #connect) -[At the end](#At the end)

Conclusion

You should use lambda for the argument of connect () in PyQt.

Introduction

This is an article about event handling of the wrapper library PyQt for handling the C ++ GUI framework Qt in Python. Each Qt class has several events that fire at specific times. This is called a signal. Ignition of signal is called emit. Nothing happens just by emitting, so you need to connect the signal to "something". connect is a function and passes "some processing" as an argument. An example is shown below.

#closePushButton inherits from QPushButton class
#In the following, the signal called clicked is self.close()Is connecting the function
closePushButton.clicked.connect(self.close)

The argument of the function connect () is a function called self.close ().

lambda expression

Now, Python has a syntax called lambda expressions that can generate anonymous functions. Functions can usually only be declared with a def statement, with the exception of this lambda expression. An example is shown below.

#Normal function definition
def def_add(a, b):
    return a + b

#Lambda expression
lambda_add = lambda a, b: a + b

if __name__ == "__main__":
    def_add_value = def_add(1, 2)
    lambda_add_value = lambda_add(1,2)
    print(def_add_value, lambda_add_value) #(3, 3)

Lambda expressions have a quirky syntax, but you can implement the same behavior as a normal function definition.

Difference between def statement and lambda expression

Reference: https://stackoverflow.com/questions/12264834/what-is-the-difference-for-python-between-lambda-and-regular-function

Let's print the function above.

print(def_add, lambda_add)
#<function def_add at 0x10ad214c0> <function <lambda> at 0x10ad4f700>

You can see that both are function types. The difference is that the former stores the function name def_add, while the latter is \ . This is the reason for anonymous functions. There was the following comment on stackoverflow.

lambda functions can't be pickled because they have no (unique) name associated with them. (Therefore, they can't be used with multiprocessing for example -- which has bit me with a PicklingError on more than one occasion ) – mgilson

Also, it might be worth pointing out that lambda is an expression whereas def is a statement. Since lambda is an expression, it can only contain other expressions (no statements are allowed) -- Although this is more of an issue at the programmer's level as opposed to "Why does python keep track of the difference" – mgilson

The lambda function doesn't have a unique name associated with it and can't be pickled (so it can't be used for multiprocessing-in my case I got multiple PicklingErrors (meaning not once))

It may also be worth pointing out that lambda is an expression while def is a statement. Since lambda is an expression, it can only contain expressions (it cannot contain statements). It's more of a programmer-level issue than "why Python keeps track of (or stores) the differences (def and lambda)."

What I found by investigating was that "the lambda function has no name and cannot contain statements". I didn't see any other difference in behavior.

Def and lambda as arguments of connection

Here is the main issue. From the above story, it seems that both the def function and the lambda expression are likely to behave basically the same. Then, if you use a function as an argument of connect () with PyQt, it should behave the same way ... does not. </ b> No, the behavior will be the same if you use it normally, but the behavior will change if the structure is a little complicated, and it may not work with the def function. </ b>

When is the def function useless?

To conclude, I didn't know the detailed cause and timing. </ b> I somehow understood that there seems to be a cause around the memory, but I have not been able to clarify the detailed timing and cause. As a concrete situation, when connecting a custom signal set in a class that inherits QThread, the def function did not work.

Use a lambda expression for connect

In the above situation, taking the lambda function as an argument instead of the def function solved the problem. I don't know the exact cause, but I'm guessing that if you pass the def function as an argument, it will be saved as a variable-like thing </ b> inside PyQt. On the other hand, in the case of lamda expressions, I wonder if only the expressions to be executed are saved </ b>. The following is a pattern of passing a lambda expression to connect, but reading this may give you some idea of what I mean (an image of the self.close () expression itself being passed?).


#closePushButton for def function.clicked.connect(self.close)
closePushButton.clicked.connect(lambda:self.close())

I don't know the exact cause so far, but I came to the conclusion that it basically behaves the same anyway and you should use a lambda expression </ b>.

Why you shouldn't pass the def function to connect

Here's another reason why you shouldn't use the def function as an argument to connect. signal may pass some value. At that time, it is necessary to prepare to receive the argument in the def function passed to connect. </ b> The following is an example.

#A signal called progressChanged passes an int to the connected function
self.tile_downloader.progressChanged.connect(self.update_download_progress)

def update_download_progress(self, value:int):
    self.ui.download_progressBar.setValue(value)

Arguments such as update_download_progress (100) are specified when calling a normal function. However, if you pass it as an argument to connect, the argument will not appear anywhere. </ b> PyQt needs to know that, and the code is very unreadable (unless you know that progressChanged passes an int, you can't see the behavior just by reading this connect). .. If it is a lambda expression, it will be as follows.

self.tile_downloader.progressChanged.connect(lambda v: self.update_download_progress(v))

def update_download_progress(self, value:int):
    self.ui.download_progressBar.setValue(value)

In the case of a lambda expression, if you read connect, you can see that the signal called progressChanged passes some value (if there is a variable you want to pass as an argument other than the value of signal, <https://stackoverflow.com/questions/35819538 See / using-lambda-expression-to-connect-slots-in-pyqt>).

At the end

It's been a long time, but the conclusion is to use a lambda expression for connect. </ b> That's it. There are two reasons: (1) I don't know why, but the lambda expression is more stable, and (2) lambda is more readable. If you have any opinions or information (especially regarding ①), please comment. Thank you for reading this far.