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

Saving the current value of a variable for later use in a local scope

我想根据循环变量(用于 PyQT)在循环中创建函数,但这些函数不是我想要的 "dereferencing" 循环变量。 (我不知道正确的术语,所以请原谅我的草率。)这是一个简单的例子:

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 的相应元素(在定义时,如果 a 它们不应该改变稍后修改)。正如链接中所建议的那样,func = lambda item=item: print(item) 修复了这个简单的案例,但我无法使我的 PyQT 案例使用相同的修复:

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_())

我想传递一个xml.etree元素,但是如果我使用lambda函数,我得到的只是False。通过显式创建每个按钮,一切正常。

闭包捕获变量名,而不是变量值。由于变量的值随着每次迭代而变化,因此当最终使用该函数时,它将引用 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 代码无法运行的原因是您提供的默认参数被覆盖了。这就是为什么你不应该使用带有默认参数的 lambda 来创建闭包的原因之一(如前所述 here). If you look at the signature of clicked (这是你将函数连接到的),你会注意到它需要一个 bool参数。这就是你得到 False 的原因。如果你提供一个无参数函数,那么这个布尔值会被 Qt 丢弃。使用上面的两种方法来防止这个布尔参数被传递给你的函数。

Dunes 已经为您提供了 您可以使用的代码,所以我不会重复,但我想解释一下为什么您需要这样做。

您在评论中给出的修复背后的原因如下:通常,Python 保存名称,而不是值。所以当你写一个函数

lambda: print(item)

并稍后调用它,该函数将在 调用 时打印与 ("bound to") 名称 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

这可以用作从当前范围保存值以供稍后在程序中使用的便捷方式。但问题是:这是一个 default 值。仅当您不为函数提供参数时才会使用它。继续前面的代码示例:

>>> func(3)
3
>>> item = 4
>>> func()
1
>>> func(item)
4

你懂的。

碰巧你连接的 PyQt 插槽,即 QAbstractButton.clicked() 接受一个参数:一个布尔值,指示按钮当前是否 "checked" 与否。 (在 Qt 中,按钮是一个切换控件,附加了一个二进制状态。您认为的标准按钮只是一个从不设置其切换状态的 Qt 按钮。)所以 Qt 使用一个调用您的 print_text 方法参数 False,反映了按钮的状态。一个 "proper" 解决方案是插入一个显式参数来反映:

button.clicked.connect(lambda checked, x=value: self.print_text(x))

当然,使用 partial 对象 可能对代码清晰度更好。这样你就可以以一种不能被传递给函数的参数覆盖的方式保存你的值。

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)

或者您可以使对象可调用并在其 __call__ 方法中实现 print_text,这使对象 本身 像一个函数一样工作:

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 功能的变体。