【问题标题】:Connecting signal to slot immediately causes signal to be emitted将信号连接到插槽立即导致信号被发射
【发布时间】:2014-03-27 02:26:19
【问题描述】:

我正在使用 python 3.3.3 和 pyqt5 编写程序。我已经连接了许多信号和插槽,没有问题。这导致了一个问题。我的代码如下:

   def populateVendorAndModelComboBoxes(self, vendorComboBox, modelComboBox):
    dictVendors = {}
    #for rclass in sorted(list(directory.DRV_TO_RADIO.values())):
    for rclass in list(directory.DRV_TO_RADIO.values()):
        if not issubclass(rclass, chirp_common.CloneModeRadio) and \
                not issubclass(rclass, chirp_common.LiveRadio):
            continue

        if not rclass.VENDOR in dictVendors:
            dictVendors[rclass.VENDOR] = []

        dictVendors[rclass.VENDOR].append(rclass)

    vendorComboBox.addItems(sorted(list(dictVendors)))

    def _vendorChanged(vendorCBox, vendorsDict, modelCBox):

        modelsList = vendorsDict[vendorCBox.currentText()]

        added_models = []

        modelCBox.clear()
        for rclass in modelsList:
            if rclass.MODEL not in added_models:
                added_models.append(rclass.MODEL)
        print("adding to modelCB")
        modelCBox.addItems(sorted(added_models))
        print("Done adding to modelCB")

    vendorComboBox.currentTextChanged.connect(_vendorChanged(vendorComboBox, dictVendors, modelComboBox))
    _vendorChanged(vendorComboBox, dictVendors, modelComboBox)

此代码使用供应商和模型填充组合框。供应商组合框在启动时填充。模型组合框填充了每个供应商的不同数据。每次用户选择不同的供应商时,必须使用不同的列表更新模型组合框。

应该发生什么:

当调用方法 populateVendorAndModelComboBoxes 时,程序的第一部分将供应商列表放入供应商组合框中。然后将在 currentTextChanged 信号和 _vendorChanged 插槽之间建立连接。然后应该首先调用 _vendorChanged 函数来设置模型组合框。从那时起,每当用户选择新的供应商时,都应该调用 _vendorChanged 函数。

发生了什么:

当 currentTextChanged 信号和 _vendorChanged 槽建立连接时,立即调用 _vendorChanged 函数。它不应立即调用 _vendorChanged 函数。我的任何其他信号/插槽连接都不会发生这种情况。 _vendorChanged 函数执行没有错误,然后执行点返回到 vendorComboBox.currentTextChanged.connect.... 语句,我立即收到错误 TypeError: argument 1 has unexpected type 'NoneType'。

如果我注释掉建立连接的语句,程序就可以正常工作。供应商组合框填充了供应商,模型组合框填充了列表中第一个供应商的模型。这表明 _vendorChanges 代码工作正常。

我有两个问题。为什么 connect 语句会导致 _vendorChanged 函数立即执行?错误信息的原因是什么?

【问题讨论】:

    标签: python pyqt python-3.3 pyqt5


    【解决方案1】:

    基于 ekhumoro 的回答,您还可以让信号将 currentText 传递给 lambda 函数。这意味着您只需将文本传递给函数,而不必稍后再获取 currentText。

    def _vendorChanged(vendorText, vendorsDict, modelCBox):
    
        modelsList = vendorsDict[vendorText]
    
        added_models = []
    
        modelCBox.clear()
        for rclass in modelsList:
            if rclass.MODEL not in added_models:
                added_models.append(rclass.MODEL)
        print("adding to modelCB")
        modelCBox.addItems(sorted(added_models))
        print("Done adding to modelCB")
    
    vendorComboBox.currentTextChanged[str].connect(
        lambda vendorText: _vendorChanged(vendorText, dictVendors, modelComboBox))
    

    另外,如果您不需要在每次发出信号时根据 lambda 函数的当前范围更新对 dictVendors 和 modelComboBox 的引用,您可以将它们排除在参数列表之外,让 _vendorChanged 函数简单从它的父范围继承它们(这与 lambda 的父范围相同......所以我不确定有什么区别......)。这样做的吸引力在于您不再需要 lamda 为信号提供可调用的...您可以直接给它 _vendorChanged 函数:

    def _vendorChanged(vendorText):
    
        modelsList = dictVendors[vendorText]
    
        added_models = []
    
        modelComboBox.clear()
        for rclass in modelsList:
            if rclass.MODEL not in added_models:
                added_models.append(rclass.MODEL)
        print("adding to modelCB")
        modelComboBox.addItems(sorted(added_models))
        print("Done adding to modelCB")
    
    vendorComboBox.currentTextChanged[str].connect(_vendorChanged)
    

    希望有帮助!

    【讨论】:

    • 您的回答和@ekhumoro 非常有帮助。你的简化了功能。我仍然坚持一件事。为什么消除 vendorDict 和 modelCBox 允许我消除 lambda?或者为什么让他们强迫我需要使用 lambda?我可以将您的两个回复都标记为答案吗?
    • 这与函数是python中的对象这一事实有关。假设您使用 def myFunc(arg): return arg*2 或与 lambda 相同的东西定义了一个函数:myFunc = lambda arg: arg*2 ...当您在 Python 解释器中仅键入 myFunc 时,它将返回 main.MyFunc>或 main.lambda>。这个函数对象没有被调用,它只是一个函数的定义,可以存储、传递、重命名等。当你通过在它后面加上括号并可能传递参数来调用它时,结果就是返回的结果.
    • 返回值往往不是函数,而是bool、int、string、list,如果没有return语句则为None。事实上,如果您主要编写返回其他函数的函数,那么您就是在实现函数式编程。无论如何,Signals 必须连接到 Slots 并且 Slot 必须是可调用的,基本上它必须是一个未调用的函数。这样,每当发出信号时,就会调用插槽......所以对于 checkbox.clicked.connect(slot),当单击框时,插槽就像 slot() 一样被调用。如果它是 checkbox.clicked[bool].connect(slot) 那么它会被称为 slot(True) 如果选中。
    • lambda 用于动态生成快速可调用函数,其优点是您可以将参数传递给另一个函数。这对 Signals 和 Slots 很有用,因为它允许您将函数用作 Slots,它接受比信号发出的更多参数。如果没有 lambda,您的 slot 函数必须采用 1 个或 0 个参数(取决于您是否连接到携带数据的信号)。使用 lambda,lambda 就像一个代理,提供信号所需的接口(0 或 1 个输入),同时为您的函数提供额外的参数。
    • 在 lambda get 被调用之前,lambda 内部的函数不会被调用,就像函数定义的内容在函数被调用之前不会被执行......任何这些都有意义?
    【解决方案2】:

    错误是由于尝试连接到函数调用的结果(在本例中为None)而不是函数对象本身引起的。当然,这也解释了为什么会立即执行该函数。

    您应该将函数调用包装在 lambda 中,如下所示:

        vendorComboBox.currentTextChanged.connect(
            lambda: _vendorChanged(vendorComboBox, dictVendors, modelComboBox))
    

    【讨论】:

    • 添加 lambda 解决了我的问题,但我不明白为什么。为什么我的语句试图连接到函数调用的结果而不是函数本身?跟闭包有关系吗?或者以我写_vendorChanged的方式?我的其他连接语句都没有这种方式。我注意到当我在这个连接语句上放置一个断点时,我的调试器说 _vendorChanged 是一个函数,而对于其他连接语句,调试器说我正在连接到一个“绑定方法”。我不明白为什么其他人没有 lambda,但这个没有。
    【解决方案3】:

    优秀的答案,但只想添加这个解释:

    在做

    vendorComboBox.currentTextChanged.connect(_vendorChanged(vendorComboBox, dictVendors, modelComboBox))
    

    和做的一样

    obj = _vendorChanged(vendorComboBox, dictVendors, modelComboBox)
    vendorComboBox.currentTextChanged.connect(obj)
    

    现在您应该看到 obj 不是一个函数,而是使用 3 个参数调用您的 _vendorChanged 函数的结果。这意味着您得到立即触发信号的印象,从而调用您的函数,但实际上它只是您的函数按指示执行。第二个问题是_vendorChanged 没有返回任何东西,所以obj 实际上是None。由于您尝试将信号连接到无,因此您会收到此错误。解决方案在其他答案中给出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-10
      • 2012-11-15
      • 1970-01-01
      相关资源
      最近更新 更多