问题
I am writing a program with python 3.3.3 and pyqt5. I have connected many signals and slots with no problem. This one is causing a problem. My code follows:
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)
This code populates comboboxes with vendors and models. The vendor combobox is populated at startup. The model combobox is filled with different data for each vendor. Every time the user picks a different vendor, the model combobox must be updated with a different list.
What should happen:
When the method populateVendorAndModelComboBoxes is called, the first part of the program puts the vendor list in the vendor combobox. Then a connection will be made between the currentTextChanged signal and the _vendorChanged slot. Then the _vendorChanged function should be initially called to setup the Model combobox. From then on, the _vendorChanged function should be called whenever the user picks a new vendor.
What is happening:
When the connection is made between the currentTextChanged signal and the _vendorChanged slot, the _vendorChanged function is immediately called. It should not immediately call the _vendorChanged function. This does not happen with any of my other signal / slot connections. The _vendorChanged function is executed with out error, then the execution point drops back to the vendorComboBox.currentTextChanged.connect.... statement and I immediately get an error TypeError: argument 1 has unexpected type 'NoneType'.
If I comment out the statement that makes the connection, the program works without error. The vendor combobox is filled with vendors and the model combobox is filled with models of the first vendor in the list. That indicates that the _vendorChanges code is working properly.
I have two questions. Why does the connect statement cause the _vendorChanged function to be immediately executed? What is the cause of the error message?
回答1:
Building on ekhumoro's answer, you could also let the signal pass the currentText to the lambda function. This means you would just pass the text into the function and not have to get the currentText later.
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))
Also, if you don't need the references to dictVendors and modelComboBox to update based on the current scope of the lambda function every time the signal is emitted, you could leave them out of the parameter list and let the _vendorChanged function simply inherit them from it's parent scope (which is the same as the lambda's parent scope... so I'm not sure there's be any difference...). The appeal of this is that you no longer need the lamda to provide the signal with a callable... you can give it the _vendorChanged function directly:
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)
Hope that helps!
回答2:
The error is caused by attempting to connect to the result of a function call (which in this case is None
), instead of the function object itself. Of course, this also explains why the function is executed immedaitely.
You should wrap the function call in a lambda
, like this:
vendorComboBox.currentTextChanged.connect(
lambda: _vendorChanged(vendorComboBox, dictVendors, modelComboBox))
回答3:
Excellent answers but just want to add this explanation:
Doing
vendorComboBox.currentTextChanged.connect(_vendorChanged(vendorComboBox, dictVendors, modelComboBox))
is the same as doing
obj = _vendorChanged(vendorComboBox, dictVendors, modelComboBox)
vendorComboBox.currentTextChanged.connect(obj)
Now you should see that obj
is not a function but the result of calling your _vendorChanged
function with the 3 parameters. This means that you get the impression that the signal is immediately fired, thereby calling your function, but in fact it's just your function executing as directed. The second problem is that _vendorChanged
does not return anything so obj is in fact None. Since you are trying to connect a signal to None, you get this error. The solution is given in other answers.
来源:https://stackoverflow.com/questions/22004465/connecting-signal-to-slot-immediately-causes-signal-to-be-emitted