我想在循环中创建函数,具体取决于循环变量(与PyQT一起使用),但函数不是"解引用"我想要的循环变量。 (我不知道正确的术语,所以原谅我的邋。。)这是一个简单的例子:
a = [1, 2, 3, 4]
b = []
for item in a:
func = lambda: print(item)
b.append(func)
print(a)
for func in b:
func()
我希望b
成为一个功能列表,每个功能都打印a
的相应元素(在定义时,如果{{1}它们不应该改变稍后修改)。正如链接中所建议的那样,a
修复了这个简单的案例,但我不能使我的PyQT案例使用相同的修复:
func = lambda item=item: print(item)
我想传递一个import sys
import xml.etree.ElementTree as ET
from PyQt4 import QtCore, QtGui
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.tree = ET.fromstring('<root><a>1</a><a>2</a><a>3</a><a>4</a></root>')
self.create_widgets()
self.w = None
def create_widgets(self):
self.buttons = []
# THIS DOESN'T WORK
for value in self.tree.findall('a'):
button = QtGui.QPushButton(value.text)
button.clicked.connect(lambda x=value: self.print_text(x))
self.buttons.append(button)
# THIS DOES WORK:
#values = []
#for value in self.tree.findall('a'):
# values.append(value)
#button = QtGui.QPushButton(values[0].text)
#button.clicked.connect(lambda: self.print_text(values[0]))
#self.buttons.append(button)
#button = QtGui.QPushButton(values[1].text)
#button.clicked.connect(lambda: self.print_text(values[1]))
#self.buttons.append(button)
#button = QtGui.QPushButton(values[2].text)
#button.clicked.connect(lambda: self.print_text(values[2]))
#self.buttons.append(button)
#button = QtGui.QPushButton(values[3].text)
#button.clicked.connect(lambda: self.print_text(values[3]))
#self.buttons.append(button)
box = QtGui.QHBoxLayout()
for i in self.buttons:
box.addWidget(i)
central_widget = QtGui.QWidget()
central_widget.setLayout(box)
self.setCentralWidget(central_widget)
def print_text(self, value):
print(value)
print(value.text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = Main()
myapp.show()
sys.exit(app.exec_())
元素,但如果我使用lambda函数,我得到的只是xml.etree
。通过明确创建每个按钮,一切正常。
答案 0 :(得分:4)
Closures捕获变量名称,而不是变量值。由于变量的值随着每次迭代而变化,因此当最终使用该函数时,它将引用item
的当前值。要解决这个问题,您需要在新范围内创建您的函数(另一个函数)。
def create_printer(item):
def printer():
print(item)
return printer
a = [1, 2, 3, 4]
b = []
for item in a:
func = create_printer(item)
b.append(func)
print(a)
for func in b:
func()
但是,您只是真正为已存在的函数提供参数。因此,您可以使用functools.partial
代替。
from functools import partial
a = [1, 2, 3, 4]
b = []
for item in a:
func = partial(print, item)
b.append(func)
print(a)
for func in b:
func()
您的Qt代码不起作用的原因是因为您提供的默认参数被覆盖。这就是为什么你不应该使用带有默认参数的lambdas来创建闭包的原因之一(如上所述here)。如果您查看clicked
的签名(这是您将函数连接到的那个),您会注意到它需要bool
个参数。这就是你得到False
的原因。如果你提供一个no-args函数,那么Qt就会抛弃这个布尔值。使用上面的两种方法来防止这个布尔参数传递给你的函数。
答案 1 :(得分:3)
Dunes已经根据您可以使用的代码给了a good answer,所以我不会重述,但我想解释为什么需要这样做。
注释中给出的修复背后的原因如下:通常,Python保存名称,而不是值。所以当你写一个函数
lambda: print(item)
并稍后调用它,该函数将打印在调用时名称item
与(“绑定”)相关联的任何事件。这应该明确说明发生了什么:
>>> item = 1
>>> func = lambda: print(item)
>>> func()
1
>>> item = 2
>>> func()
2
但是,参数的默认值是一个例外。当你写
lambda item=item: print(item)
Python在函数声明时保存绑定到名称item
的任何值,如果在调用时没有给它一个参数,则将其用作默认值功能。
>>> item = 1
>>> func = lambda item=item: print(item)
>>> func()
1
>>> item = 2
>>> func()
1
这可以作为一种方便的方法来保存当前作用域中的值,以便以后在程序中使用。但这里有一个问题:它是默认值。只有在不为函数提供参数时才会使用它。继续前面的代码示例:
>>> func(3)
3
>>> item = 4
>>> func()
1
>>> func(item)
4
你明白了。
碰巧你正在连接的PyQt插槽,即QAbstractButton.clicked()
, 接受一个参数:一个布尔值,指示按钮当前是否“被选中”。 (在Qt中,按钮是一个切换控件,附加了二进制状态。你认为标准按钮只是一个Qt按钮,它永远不会设置其切换状态。)所以Qt用一个调用你的print_text
方法参数False
,反映按钮的状态。一个“正确”的解决方案是插入一个反映出:
button.clicked.connect(lambda checked, x=value: self.print_text(x))
当然,使用partial
对象as Dunes suggested的代码清晰度可能更好。这样你就可以通过传递给函数的参数覆盖的方式保存你的值。
button.clicked.connect(partial(self.print_text, value))
另一种选择是创建自己的存储值的对象,并将该对象的方法传递给connect()
。您可以在此对象中定义print_text
函数:
class MyPartial:
def __init__(self, value):
self.value = value
def print_text(self):
# ...
...
button.clicked.connect(MyPartial(value).print_text)
或者您可以使对象可调用并在其print_text
方法中实现__call__
,这使得对象本身就像一个函数:
class MyPartial:
def __init__(self, value):
self.value = value
def __call__(self):
# equivalent of print_text() goes here
...
button.clicked.connect(MyPartial(value))
或者你可以给对象一个对具有实际print_text()
方法的东西的引用,如果这更适合你的程序。
class MyPartial:
def __init__(self, func, value):
self.func = func
self.value = value
def __call__(self):
self.func(self.value)
...
button.clicked.connect(MyPartial(self.print_text, value))
等等。所有这些选项都是内置functools.partial
所做的变体。