PyQt中QPushButton绑定独立参数异常问题求助
问题原理
这是Python中lambda表达式的延迟绑定特性导致的:lambda里引用的变量,不会在定义时就捕获当前值,而是在lambda被调用(也就是按钮点击时)才会去查找变量的当前值。如果变量在后续代码中被修改,所有绑定的lambda都会使用变量最终的取值。
第一次场景的问题与解决
原代码中,setButton里的lambda直接引用vessel[1]:
def setButton(vessel): button = QPushButton(name + value) button.clicked.connect(lambda: vessel[2](vessel[1]))
vessel是可变列表,在setButtons的循环中,vessel[1]被不断更新为下一个obj。当按钮被点击时,vessel[1]已经是循环最后一个元素的值,所以所有按钮都触发最后一个参数的逻辑。
添加中间变量x = vessel[1]后:
def setButton(vessel): button = QPushButton(name + value) x = vessel[1] button.clicked.connect(lambda: vessel[2](x))
x是setButton函数的局部变量,每次调用setButton都会创建一个新的x,并保存当前vessel[1]的值(如果是不可变类型如字符串、数字,就是值拷贝;可变类型则是引用,但此时vessel[1]已经被赋值为当前循环的obj,后续不会被修改)。lambda捕获的是这个局部变量x,每个按钮的lambda对应各自的x,因此能正确触发对应参数的逻辑。
第二次场景的问题与解决
在update函数的循环中,绑定的lambda引用循环变量obj:
def update(self): for sub in self.subList: for l in sub.subVessel: obj = l[0] func = l[1] button = l[2] button.clicked.disconnect() button.clicked.connect(lambda: func(obj))
这里的obj是循环体的变量,每次循环都会被重新赋值。当按钮点击时,循环已经结束,obj保留的是最后一次循环的取值,导致所有按钮都用最后一个参数。
解决方法:利用lambda的默认参数捕获当前值
默认参数的求值时机是lambda定义时,而非调用时。修改绑定代码:
button.clicked.connect(lambda o=obj, f=func: f(o))
这样每次循环创建lambda时,o和f会被赋值为当前循环的obj和func的值,后续循环修改obj和func不会影响已创建的lambda。
也可以用局部变量包裹,但默认参数的写法更简洁可靠:
# 局部变量包裹的写法 x = obj f = func button.clicked.connect(lambda: f(x))
如果局部变量写法无效,大概率是循环作用域导致的变量复用问题,此时默认参数的方法更稳妥。
内容的提问来源于stack exchange,提问作者Brandon Zamora

