保存变量的当前值以供以后在本地范围内使用

时间:2016-05-01 12:57:15

标签: python pyqt4 name-binding

我想在循环中创建函数,具体取决于循环变量(与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。通过明确创建每个按钮,一切正常。

2 个答案:

答案 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问题

您的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所做的变体。