如何将lambda函数与pyqt“连接”一起使用。当小部件在循环中安装时,功能?

发布于 2025-02-07 02:53:35 字数 1727 浏览 2 评论 0 原文

我正在PYQT项目中创建一个依赖情况的小部件列表(在这里,它以小部件类型的列表表示)。我不知道每种类型所需的小部件的数量,也不知道它们的类型和小部件的数量。

创建它们并不复杂,但是当用户想要更改它们时,我必须抓住它们的新价值时会遇到麻烦。

这是我问题的最低可再现示例:

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QCheckBox, QSpinBox, QWidget
import sys

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()
        
        for widgetNumber in range(len(widgetList)):
        
            if widgetList[widgetNumber] == 'QCheckBox':
                widget = QCheckBox()
                widget.stateChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.isChecked()))
            
            elif widgetList[widgetNumber] == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.value()))
            
            layout.addWidget(widget)
            
        self.setLayout(layout)
    
    def argumentChanged(self, widgetNumber, value):
        print("The widget number", widgetNumber, "that is a", self.widgetList[widgetNumber], "has been modified! New value:", value)
    
def window():
    app = QApplication(sys.argv)
    
    widgetList = ['QCheckBox', 'QCheckBox', 'QSpinBox']
    widget = MyWidget(widgetList)
    widget.show()
    
    sys.exit(app.exec_())
    
if __name__ == '__main__':
    window()

在这种情况下,lambda函数中的widgetnumber的价值将始终是循环的最后一次迭代。因此,如果我有4个小部件,我将在每个lambda函数中具有3个值。目的是具有当前小部件的值。

我还试图将lambda函数的表达式更改为这些:

lambda widgetNumber: self.argumentChanged(widgetNumber, widget.value()))

但是情况变得陌生:当增加旋转框的值时,小部件数字在增加,而在减少它时反之亦然。

I am creating in a PyQt project a list of widgets that depend on the situation (here it is represented with a list of widget types). I don't know by advance the number of widgets needed, neither their type nor the number of widgets for each type.

Creating them is not complicated but I am having trouble when I have to catch their new value when the user wants to change them.

Here is the minimum reproducible example of my problem:

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QCheckBox, QSpinBox, QWidget
import sys

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()
        
        for widgetNumber in range(len(widgetList)):
        
            if widgetList[widgetNumber] == 'QCheckBox':
                widget = QCheckBox()
                widget.stateChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.isChecked()))
            
            elif widgetList[widgetNumber] == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.value()))
            
            layout.addWidget(widget)
            
        self.setLayout(layout)
    
    def argumentChanged(self, widgetNumber, value):
        print("The widget number", widgetNumber, "that is a", self.widgetList[widgetNumber], "has been modified! New value:", value)
    
def window():
    app = QApplication(sys.argv)
    
    widgetList = ['QCheckBox', 'QCheckBox', 'QSpinBox']
    widget = MyWidget(widgetList)
    widget.show()
    
    sys.exit(app.exec_())
    
if __name__ == '__main__':
    window()

In this situation, the value of widgetNumber in the lambda functions will always be the last iteration of the loop. So if I had 4 widgets, I will have a 3 value inside every lambda function. The objective is to have the value of the current widget.

I also tried to change the expressions of the lambda functions to these:

lambda widgetNumber: self.argumentChanged(widgetNumber, widget.value()))

but the situation becomes stranger: when increasing the value of a SpinBox, the widget number is increasing, and vice-versa when decreasing it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

韶华倾负 2025-02-14 02:53:35

lambda内使用的变量始终在执行时评估: widgetnumber Will 始终是for循环中的最后一个值。

此外,信号可以具有参数,如果您在lambda中使用位置参数,则该参数将是该信号发射时的值: lambda widgetnumber:将导致spinbox值或状态。复选框。

如果您需要“静态”参数,则需要使用关键字参数。由于信号有参数,因此也无需再次调用getter:

lambda value, widgetNumber=widgetNumber: self.argumentChanged(widgetNumber, value)

请记住,只有在没有其他情况下才能使用lambdas, simpler clearer >清洁器)解决方案。
在这种情况下,我们可以使用 sender() QT提供的功能,该功能返回发出信号的对象。

在下面的示例中,我简化了整个循环,并使用a custom属性直接在小部件上设置数字(请注意,也可以使用一个基本属性,但属性与QT更一致)。

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()

        for number, widgetType in enumerate(widgetList):

            if widgetType == 'QCheckBox':
                widget = QCheckBox()
                widget.toggled.connect(self.argumentChanged)

            elif widgetType == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(self.argumentChanged)

            widget.setProperty('widgetNumber', number)
            layout.addWidget(widget)

        self.setLayout(layout)

    def argumentChanged(self, value):
        widget = self.sender()
        widgetNumber = widget.property('widgetNumber')
        print("The widget number", widgetNumber, 
            "that is a", self.widgetList[widgetNumber], 
            "has been modified! New value:", value)
    

The variables used inside a lambda are always evaluated at execution: widgetNumber will always be the last value in the for loop.

Also, signals can have arguments, and if you use a positional argument in the lambda, that argument will be the value of that signal when it's emitted: lambda widgetNumber: will result in the spinbox value or the state of the check box.

If you need "static" arguments, you need to use keyword arguments. Since the signal has arguments, there's also no need to call the getter again:

lambda value, widgetNumber=widgetNumber: self.argumentChanged(widgetNumber, value)

Remember that lambdas should be preferably used only when there is no other, simpler (clearer or cleaner) solution.
In this case, we can use the sender() function provided by Qt, which returns the object that emitted the signal.

In the following example I've simplified the whole loop, and used a custom property to set the number directly on the widget (note that also a basic attribute could be used, but properties are more consistent with Qt).

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()

        for number, widgetType in enumerate(widgetList):

            if widgetType == 'QCheckBox':
                widget = QCheckBox()
                widget.toggled.connect(self.argumentChanged)

            elif widgetType == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(self.argumentChanged)

            widget.setProperty('widgetNumber', number)
            layout.addWidget(widget)

        self.setLayout(layout)

    def argumentChanged(self, value):
        widget = self.sender()
        widgetNumber = widget.property('widgetNumber')
        print("The widget number", widgetNumber, 
            "that is a", self.widgetList[widgetNumber], 
            "has been modified! New value:", value)
    
七度光 2025-02-14 02:53:35

因此,这里发生的事情

widget.stateChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.isChecked()))

是: widgetNumber 与循环的封装中使用的变量相同。到发出 widget.statechanged 信号时,循环的上述已经结束(显然), widgetnumber is 3

lambda widgetnumber:self.argumentChanged(widgetnumber,widget.value()))

但是情况变得陌生:当增加旋转框的价值时,小部件数量增加,而降低时的副词。

这是因为 value echanged 信号通过新值的整数值为您的函数/插槽作为第一个参数( widgetNumber 参数),然后将其传递到 self.argument.argument.argumentChanged 中。

为了将 widgetnumber 的值保持在循环的每次迭代期间,您需要使用 widgetnumber 作为[lambda]函数的默认参数。默认参数将在首次定义函数时评估,而不是每次调用时,因此您可以访问 widgetNumber 在循环的每次迭代期间[lambda]函数时的内容。

请注意,您需要使用嵌套功能。如果您只尝试具有一个默认参数的一个函数,则该参数将被设置为小部件的更改值,因为QT5将其作为第一个参数传递。

外部函数是具有 widgetnumber 作为其默认参数的一个功能,只需定义一个内部函数,然后返回(不调用)。它们是否功能都没关系。嵌套的lambdas很难阅读。

这很令人困惑,但请参阅:

    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()
        
        for widgetNumber in range(len(widgetList)):
            
            if widgetList[widgetNumber] == 'QCheckBox':
                widget = QCheckBox()
                
                # A normal function with a nested lambda function
                def handle_state_changed(default_arg=widgetNumber):
                    return lambda state: print(f"QCheckBox state changed to {state}. default_arg (aka widgetNumber) = {default_arg})")
                
                widget.stateChanged.connect(handle_state_changed())
            
            elif widgetList[widgetNumber] == 'QSpinBox':
                widget = QSpinBox()
                
                # A normal function nested within another normal function
                # Whether they are lambda functions or not doesn't matter
                def handle_value_changed(default_arg=widgetNumber):
                    def inner(value):
                        print(f"QSpinBox value changed to {value}. default_arg (aka widgetNumber) = {default_arg})")
                    
                    return inner
                
                widget.valueChanged.connect(handle_value_changed())
            
            layout.addWidget(widget)
            
        self.setLayout(layout)

另请参见为什么在具有不同值的循环中定义的lambdas都返回相同的结果?

So what's happening here:

widget.stateChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.isChecked()))

is that widgetNumber is the same variable used in the enclosing for loop. By the time the widget.stateChanged signal is emitted, the aforementioned for loop has already ended (obviously), and widgetNumber is 3.

lambda widgetNumber: self.argumentChanged(widgetNumber, widget.value()))

but the situation becomes stranger: when increasing the value of a SpinBox, the widget number is increasing, and vice-versa when decreasing it.

That's because the valueChanged signal passes the new value's integer value into your function/slot as the first argument (the widgetNumber parameter), which is then passed into self.argumentChanged.

In order to keep the value of widgetNumber to what it was during each iteration of the loop, you need to use widgetNumber as the default argument of the [lambda] function. Default arguments are evaluated when the function is first defined, not each time it is called, so you can access what widgetNumber was at the time of the [lambda] function during each iteration of the loop.

Note that you need to use a nested function. If you try to just have one function with a default argument, the argument will be set as the widget's changed value, since Qt5 passes it as the first argument.

The outer function, which is the one with widgetNumber as its default argument, simply defines an inner function, and returns that (without calling it). It doesn't matter if they're lambda functions or not. Having nested lambdas is just difficult to read.

It's pretty confusing, but have a play around with this:

    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()
        
        for widgetNumber in range(len(widgetList)):
            
            if widgetList[widgetNumber] == 'QCheckBox':
                widget = QCheckBox()
                
                # A normal function with a nested lambda function
                def handle_state_changed(default_arg=widgetNumber):
                    return lambda state: print(f"QCheckBox state changed to {state}. default_arg (aka widgetNumber) = {default_arg})")
                
                widget.stateChanged.connect(handle_state_changed())
            
            elif widgetList[widgetNumber] == 'QSpinBox':
                widget = QSpinBox()
                
                # A normal function nested within another normal function
                # Whether they are lambda functions or not doesn't matter
                def handle_value_changed(default_arg=widgetNumber):
                    def inner(value):
                        print(f"QSpinBox value changed to {value}. default_arg (aka widgetNumber) = {default_arg})")
                    
                    return inner
                
                widget.valueChanged.connect(handle_value_changed())
            
            layout.addWidget(widget)
            
        self.setLayout(layout)

See also Why do lambdas defined in a loop with different values all return the same result?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文