【问题标题】:Using lambda expression to connect slots in pyqt使用 lambda 表达式连接 pyqt 中的插槽
【发布时间】:2016-06-19 13:47:32
【问题描述】:

我正在尝试将插槽与 lambda 函数连接起来,但它没有按我预期的方式工作。在下面的代码中,我成功地正确连接了前两个按钮。对于我循环连接的后两个,这是错误的。在我之前有人有同样的问题 (Qt - Connect slot with argument using lambda),但这个解决方案对我不起作用。我盯着屏幕看了半个小时,但我不知道我的代码有何不同。

class MainWindow(QtGui.QWidget):
    def __init__(self):
        super(QtGui.QWidget, self).__init__()

        main_layout = QtGui.QVBoxLayout(self)

        # Works:
        self.button_1 = QtGui.QPushButton('Button 1 manual', self)
        self.button_2 = QtGui.QPushButton('Button 2 manual', self)
        main_layout.addWidget(self.button_1)
        main_layout.addWidget(self.button_2)

        self.button_1.clicked.connect(lambda x:self.button_pushed(1))
        self.button_2.clicked.connect(lambda x:self.button_pushed(2))

        # Doesn't work:
        self.buttons = []
        for idx in [3, 4]:
            button = QtGui.QPushButton('Button {} auto'.format(idx), self)
            button.clicked.connect(lambda x=idx: self.button_pushed(x))
            self.buttons.append(button)
            main_layout.addWidget(button)


    def button_pushed(self, num):
        print 'Pushed button {}'.format(num)

按下前两个按钮会产生“Pushed button 1”和“Pushed button 2”,其他两个都产生“Pushed button False”,尽管我预计会出现 3 和 4。

我也没有完全理解 lambda 机制。究竟是什么联系起来的?指向由 lambda 生成的函数的指针(参数被替换)还是在信号触发时评估 lambda 函数?

【问题讨论】:

    标签: python lambda pyqt


    【解决方案1】:

    QPushButton.clicked 信号发出一个指示按钮状态的参数。当您连接到您的 lambda 插槽时,您分配给 idx 的可选参数将被按钮的状态覆盖。

    相反,将您的连接设为

    button.clicked.connect(lambda state, x=idx: self.button_pushed(x))
    

    这样按钮状态被忽略,正确的值被传递给你的方法。

    【讨论】:

    • “状态”这个词的真正含义是什么?
    • @ioaniatr 当您连接到clicked 信号时,该信号具有描述为here 的签名。如您所见,当发出信号时,会提供一个参数,其中包含按钮的状态(按钮是否被选中)。我的代码中的变量可以随心所欲地调用,但它必须存在以便 Qt 不会覆盖下一个参数 (x=idx) 并处于选中状态。
    • 当我尝试从 QMenu.addAction() 连接一个 QAction 时,我得到一个异常:missing 1 required positional argument: 'checked' 触发我的菜单项时。 action.triggered.connect(lambda checked, service=service: printService(service))我做错了什么?
    • @grego,如果你还没有解决这个问题。我建议使用 functools.partial:action.triggered.connect(functools.partial(printService, service))
    【解决方案2】:

    小心!只要您将信号连接到引用 self 的 lambda 插槽,您的小部件就不会被垃圾收集!这是因为 lambda 创建了一个闭包,其中包含对小部件的另一个无法收集的引用。

    因此,self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p)) 非常邪恶:)

    【讨论】:

    • 我花了两天时间对代码进行注释和取消注释,结果发现这是我正在调查的“泄漏”错误,然后我遇到了您的分析,这正是我最终得出的结论。现在关闭以更改所有现有代码.... :(
    • 记录一下,如果lambdaself 传递给某个外部函数,同样的问题:self.someUIwidget.someSignal.connect(lambda someNonSelfMethod(self))
    • 那有什么替代方案?
    • @Esostack:取决于您想要实现的目标。如果您只想在槽中检测哪个对象发出了信号,可以使用QObject.sender() 方法。此外,您可以将信息存储在小部件属性中并在插槽函数中访问它们,请参见此示例:stackoverflow.com/questions/49446832/…
    • @Rhdr 另一个值得一提的讨论在这里stackoverflow.com/questions/33304203/…
    【解决方案3】:

    老实说,我也不确定您在这里使用 lambda 出了什么问题。我认为这是因为 idx(设置自动按钮时的循环索引)超出范围并且不再包含正确的值。

    但我认为你不需要这样做。看起来您使用 lambda 的唯一原因是您可以将参数传递给 button_pushed(),以确定它是哪个按钮。有一个函数 sender() 可以在 button_pushed() 槽中调用,它标识哪个按钮发出了信号。

    这里有一个例子,我认为它或多或少符合你的目标:

    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    import sys
    
    class MainWindow(QWidget):
        def __init__(self):
            super(QWidget, self).__init__()
    
            main_layout = QVBoxLayout(self)
    
            self.buttons = []
    
            # Works:
            self.button_1 = QPushButton('Button 1 manual', self)
            main_layout.addWidget(self.button_1)
            self.buttons.append(self.button_1)
            self.button_1.clicked.connect(self.button_pushed)
    
            self.button_2 = QPushButton('Button 2 manual', self)
            main_layout.addWidget(self.button_2)
            self.buttons.append(self.button_2)
            self.button_2.clicked.connect(self.button_pushed)
    
            # Doesn't work:
            for idx in [3, 4]:
                button = QPushButton('Button {} auto'.format(idx), self)
                button.clicked.connect(self.button_pushed)
                self.buttons.append(button)
                main_layout.addWidget(button)
    
    
        def button_pushed(self):
            print('Pushed button {}'.format(self.buttons.index(self.sender())+1))
    
    
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())
    

    【讨论】:

    • (显然,创建所有按钮“自动”(在循环中)是微不足道的,而不是在循环外创建两个按钮,在循环内创建两个按钮,如果这是您的最终目标...
    • 感谢您的建议,但上面的 three_pineapples 解决方案正是我所寻找的,并解释了发生了什么。是的,没有理由不在循环中生成所有四个按钮,除了展示循环外行为如何不同的示例。
    • 我也很高兴知道你的做法出了什么问题。
    【解决方案4】:

    这很简单。检查工作代码和不工作代码。您有语法错误。

    工作代码:

    self.button_1.clicked.connect(lambda x:self.button_pushed(1))
    

    不起作用:

    button.clicked.connect(lambda x=idx: self.button_pushed(x))
    

    修复:

    button.clicked.connect(lambda x: self.button_pushed(idx))
    

    对于 lambda,您正在定义一个“x”函数并将该函数解释为“self.button_pushed(idx)”,以便在这种情况下使用参数与您的函数一起使用 (idx)。请尝试让我知道它是否有效。

    他的问题是他试图从 for 循环创建中获得不同的输出。不幸的是,它将最后一个值分配给任何名为 button 的变量,因此结果为 4。前两个正在工作,因为它们不是在 for 循环中创建的,而是单独创建的。

    并且按钮变量的名称在工作的button_1和button_2中是不同的。在 for 循环中,所有创建的按钮都将具有相同功能的名称按钮。

    他正在尝试做的解决方案如下,它就像一个魅力。

    from sys import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    buttons = []
    
    def newWin():
        window = QWidget()
        window.setWindowTitle("Lambda Loop")
        window.setFixedWidth(1000)
        window.move(175, 10)
        window.setStyleSheet("background: #161219;")
        grid = QGridLayout()
        return window, grid
    
    def newButton(text :str, margin_left, margin_right, x):
        button = QPushButton(text)
        button.setCursor(QCursor(Qt.PointingHandCursor))
        button.setFixedWidth(485)
        button.setStyleSheet(
            "*{border: 4px solid '#BC006C';" +
            "margin-left: " + str(margin_left) + "px;" +
            "margin-right: " + str(margin_right) + "px;" +
            "color: 'white';" +
            "font-family: 'Comic Sans MS';" +
            "font-size: 16px;" +
            "border-radius: 25px;" +
            "padding: 15px 0px;" +
            "margin-top: 20px;}" +
            "*:hover {background: '#BC006C'}"
        )
    
        def pushed():
            val = x
            text = QLabel(str(val))
            text.setAlignment(Qt.AlignRight)
            text.setStyleSheet(
                "font-size: 35px;" +
                "color: 'white';" +
                "padding: 15px 15px 15px 25px;" +
                "margin: 50px;" +
                "background: '#64A314';" +
                "border: 1px solid '#64A314';" +
                "border-radius: 0px;"
            )
            grid.addWidget(text, 1, 0)
        button.clicked.connect(pushed)
        return button
    
    app = QApplication(argv)
    window, grid = newWin()
    
    def frame1(grid):
        for each in [3, 4]:
            button = newButton('Button {}'.format(each), 150, 150, each)
            buttons.append(button)
            pass
        b_idx = 0
        for each in buttons:
            grid.addWidget(each, 0, b_idx, 1, 2)
            b_idx += 1
    
    frame1(grid)
    
    window.setLayout(grid)
    
    window.show()
    
    exit(app.exec())
    

    我把所有东西放在一个地方,以便所有人都能看到。告诉你想做什么比猜测更容易。 (您也可以在 Frame 函数的列表中添加新变量,它会为您创建更多具有不同值和功能的按钮。)

    【讨论】:

    • 避免争论选票。投票是私人的,不需要证明是合理的。如果您收到紫外线,您会提出同样的要求吗?不,同样的规则适用于 DV。如果您在帖子中放置不相关的信息,那么这将被视为噪音。如果你的帖子很好,那么很长一段时间,你会得到比 DV 更多的紫外线,如果不是,那么你会得到相反的结果。
    • 另一方面,我在您的答案中没有做出更好的贡献,因为它是已接受答案的不太好的版本,因为例如您的解决方案在 for 循环中失败,这与已接受的答案不同那个案子。
    • 显然你投了反对票。我不是在争论,但投票给人们是不公平的,尤其是像我这样新注册的人,因为我已经做出了真实的陈述。先试试代码。
    • 1.我没有投票,如果我投票了,我也不会这么说。这是任何用户的权利。 2. 投票是由帖子给出的,而不是像所有 SO 规则所表明的那样由用户给出。在这里,我们不根据创建帖子的人来评价帖子,而是根据帖子本身的质量来评价帖子。在这里,无论您是新用户还是老用户,无论您是初学者还是专家,都无关紧要,因为帖子的贡献。请阅读How to Answer 并查看tour
    猜你喜欢
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-31
    相关资源
    最近更新 更多