【问题标题】:tkinter passing integer by value intead of referencetkinter 按值而不是引用传递整数
【发布时间】:2020-03-16 17:43:38
【问题描述】:

我提供了可以重现的最少错误代码。

我正在使用 python 3。

我有一个 printValue 函数,它输出它接收到的参数。我还有一个值列表。

使用 tkinter,我创建了一个框架,并根据值在框架内生成一个表格。当我必须添加一个输出行号的按钮时,问题就出现了。每个按钮在应该输出相应的行号时输出值 5。

import tkinter as tk

def printValue(param):
    print(param)

Values=[[1],[2],[3],[4],[5]]
mainGuiWindow = tk.Tk()

frame = tk.Frame(mainGuiWindow)
frame.pack()

def refreshFrame():
    global frame
    global Values
    frame.destroy()
    frame = tk.Frame(mainGuiWindow)
    frame.pack()
    tk.Label(frame,text="Sl. No").grid(row=0,column=0)
    tk.Label(frame,text="Value").grid(row=0,column=1)
    tk.Label(frame,text="Print Entry").grid(row=0,column=2)

    numberOfEntries = 0
    for entry in Values:
        numberOfEntries += 1
        tk.Label(frame,text=numberOfEntries).grid(row=numberOfEntries,column=0)
        tk.Label(frame,text=entry[0]).grid(row=numberOfEntries,column=1)
        tk.Button(frame, text="Print",command=lambda: printValue(numberOfEntries)).grid(row=numberOfEntries,column=2)

refreshFrame()

mainGuiWindow.mainloop() 

问题似乎是按钮中的函数参数绑定到变量'numberOfEntries'

如何在按钮中传递参数以使函数打印正确的值?我不想修改函数定义。我需要更改按钮调用它的方式。

【问题讨论】:

标签: python python-3.x tkinter


【解决方案1】:

这是因为您的框架是如何“刷新”的。您在此处定义页面,当您设置所有按钮和所有内容时,您会在所有内容被渲染之前遍历整个 for 循环。这将使command 成为循环中的最后一个值5。实际上,它会这样做:

def f(n):
    return n

def list_of_functions():
    x = []
    for i in range(5):
        x.append(lambda: f(i))
    return x

for func in list_of_functions():
    func()

4
4
4
4
4

这通常是函数的一种行为,因为它采用的是您上次定义的 numberOfEntries 的值,而不是您认为定义它的值。要使用等效的“正常”功能来展示这一点:

# Note that there's no argument here, because
# you haven't given your lambda an argument
def f():
    # does a global lookup for value g
    return g

def create_funcs():
    funcs = []
    for g in range(5):
        funcs.append(f)
    return funcs

for func in create_funcs():
    func()

4
4
4
4
4

这里的解决方法是将该值物理绑定到函数在定义时。这可以通过functools.partial 完成。先举个简单的例子:

from functools import partial 

def f(n):
    return n

def list_of_functions():
    x = []
    for i in range(5):
        x.append(partial(f, i))
    return x

for func in list_of_functions():
    func()

0
1
2
3
4

或者,您可以向您的 lambda 添加一个输入参数并为其绑定一个默认值:


def f(n):
    return n

def list_of_functions():
    x = []
    for i in range(5):
        # note that now we have bound an input argument to our lambda
        x.append(lambda i=i: f(i))
    return x

for func in list_of_functions():
    func()
0
1
2
3
4

现在,每个调用都会相应更改。展示如何在您的代码中实现这一点:

from functools import partial


def refreshFrame():
    global frame
    global Values
    frame.destroy()
    frame = tk.Frame(mainGuiWindow)
    frame.pack()
    tk.Label(frame,text="Sl. No").grid(row=0,column=0)
    tk.Label(frame,text="Value").grid(row=0,column=1)
    tk.Label(frame,text="Print Entry").grid(row=0,column=2)

    # I've changed this to be a bit more idiomatic
    for n, (entry, *_) in enumerate(Values, start=1):
        tk.Label(frame,text=n).grid(row=n, column=0)
        tk.Label(frame,text=entry).grid(row=n, column=1)

        # this *looks* like a lambda, and still returns a function
        # but binds the value of `n` to the function, rather than relying
        # on references to a variable that you've changed
        tk.Button(frame, text="Print",command=partial(printValue, n)).grid(row=n, column=2)

【讨论】:

    猜你喜欢
    • 2013-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-11
    • 2015-03-24
    • 1970-01-01
    • 2015-11-25
    • 2019-05-25
    相关资源
    最近更新 更多