我刚刚升级了 PySide6(从 6.2.2 开始,PySide6==6.6.0),一个新行为对我的 GUI 程序造成了严重破坏。在我有
clicked
信号的每个地方,连接的插槽都会收到额外的 bool==False 参数。
x = QPushButton('New', clicked = self.new_append_title)
# ..... add x to my gui
def new_append_title(self, *args, **kwargs):
print(f' Args: {args}' )
print(f' Kwargs: {kwargs}' )
这回来了
参数:(假,)
夸格斯:{}
以前的早期版本我没有得到任何争论。我可以修复我的所有代码,因此需要一个额外的虚拟参数,但是有人知道这里发生了什么吗?我还更新了我的要求中的其他一些内容 - 比如迁移到 python 3.12,所以它可能不是 PySide 问题...
为了回应 ekhumoro,我实际上是想在这里发送和争论,并且我已经尝试了我能想到的所有排列。
self.save_button.clicked.connect(lambda *, row=row: self.save_row(row))
TypeError:SemanticTable.save_row() 缺少 1 个必需的位置参数:“已检查”
唯一对我有用的是:
self.save_button.clicked.connect(functools.partial(self.save_row, row)))
为此,我需要将 save_row 容纳为:
def save_row(self, row, checked):
检查的布尔参数排在最后,这让我完全困惑。但我并不完全理解偏颇。很多事情我不明白。
我确实可以重现不同版本的 PySide 之间行为上的这种奇怪差异。然而,与我之前对此主题的回答不同,当前的问题涉及如何建立信号连接。看来 PySide 在这方面一直高度不一致,而 PyQt 则可靠得多(至少在我测试的几个版本中)。
与当前情况的重要区别在于,通过
clicked
关键字参数建立的信号连接的行为方式与通过 connect
方法建立的显式连接不同。下面详细介绍了整个令人遗憾的混乱情况,但这里最直接的信息是:为了让您的 PySide 代码面向未来,永远不要依赖于默默删除的默认信号参数。始终假设某个未来版本将开始发送默认参数,并相应地保护您的插槽。这就是 PyQt5/6 一直以来的行为方式,而且 PySide6 似乎最终也会这样做。在我自己的代码中,我最常使用仅关键字参数来解决此问题,如下所示:
button.connect(self.slot)
...
def slot(self, *, mykeyword=True):
正如您将在下面看到的,在我测试的所有版本的 PySide 和 PyQt 中,其行为都是相同的,并且在 API 更改和一般代码改动方面可能是干扰最小的。 (但另请参阅我之前的答案了解其他几个选项 - 有一些附带条件)。
现在是血淋淋的细节。
下面是暴露问题的测试脚本。正如您将从输出中看到的,PySide2 和 PySide6 显示不同的行为,具体取决于信号连接的方式以及所使用的精确版本。好消息是,在通过
clicked
关键字参数建立连接时,PySide-6.6.0 显示了与 PyQt5/6 相同的行为。坏消息是,使用 connect
显示出与 PySide2 相同的不一致行为。另请注意,PySide2 本身在处理槽参数方面表现出一些差异:使用 connect(slot)
时,第一个参数被覆盖,而使用 clicked=slot
时,则不会。
测试脚本:
import sys
if (pkg := sys.argv[1]) == 'ps2':
from PySide2 import QtCore, QtWidgets
elif pkg == 'ps6':
from PySide6 import QtCore, QtWidgets
elif pkg == 'pq6':
from PyQt6 import QtCore, QtWidgets
else:
from PyQt5 import QtCore, QtWidgets
try:
KEYWORD = sys.argv[2] == 'kw'
except:
KEYWORD = False
print(f'{QtCore.__package__} (Qt-{QtCore.qVersion()}), keyword={KEYWORD}')
app = QtWidgets.QApplication(['Test'])
def slot1(n=2): print(f'slot1: {n=!r}')
def slot2(*args, n=2): print(f'slot2: {args=!r}, {n=!r}')
def slot3(x, n=2): print(f'slot3: {x=!r}, {n=!r}')
def slot4(*, n=2): print(f'slot4: {n=!r}')
for index in range(1, 5):
slot = globals()[f'slot{index}']
if KEYWORD:
button = QtWidgets.QPushButton(clicked=slot)
else:
button = QtWidgets.QPushButton()
button.clicked.connect(slot)
button.click()
输出:
PySide2:
$ python3 test-signals.py ps2
PySide2 (Qt-5.15.11), keyword=False
slot1: n=False
slot2: args=(), n=2
TypeError: slot3() missing 1 required positional argument: 'x'
slot4: n=2
$ python3 test-signals.py ps2 kw
PySide2 (Qt-5.15.11), keyword=True
slot1: n=2
slot2: args=(), n=2
TypeError: slot3() missing 1 required positional argument: 'x'
slot4: n=2
PySide6:
$ python3 test-signals.py ps6
PySide6 (Qt-6.2.2), keyword=False
slot1: n=False
slot2: args=(), n=2
TypeError: slot3() missing 1 required positional argument: 'x'
slot4: n=2
$ python3 test-signals.py ps6 kw
PySide6 (Qt-6.2.2), keyword=True
slot1: n=2
slot2: args=(), n=2
TypeError: slot3() missing 1 required positional argument: 'x'
slot4: n=2
$ python3 test-signals.py ps6
PySide6 (Qt-6.6.0), keyword=False
slot1: n=False
slot2: args=(), n=2
TypeError: slot3() missing 1 required positional argument: 'x'
slot4: n=2
$ python3 test-signals.py ps6 kw
PySide6 (Qt-6.6.0), keyword=True
slot1: n=False
slot2: args=(False,), n=2
slot3: x=False, n=2
slot4: n=2
PyQt5/6:
$ python3 test-signals.py pq6
PyQt6 (Qt-6.6.0), keyword=False
slot1: n=False
slot2: args=(False,), n=2
slot3: x=False, n=2
slot4: n=2
$ python3 test-signals.py pq6 kw
PyQt6 (Qt-6.6.0), keyword=True
slot1: n=False
slot2: args=(False,), n=2
slot3: x=False, n=2
slot4: n=2
$ python3 test-signals.py pq5
PyQt5 (Qt-5.15.11), keyword=False
slot1: n=False
slot2: args=(False,), n=2
slot3: x=False, n=2
slot4: n=2
$ python3 test-signals.py pq5 kw
PyQt5 (Qt-5.15.11), keyword=True
slot1: n=False
slot2: args=(False,), n=2
slot3: x=False, n=2
slot4: n=2