【问题标题】:Python - argument passed through lambda function not retaining its statePython - 通过 lambda 函数传递的参数不保留其状态
【发布时间】:2021-04-21 12:46:51
【问题描述】:

我有一个生成以下表单的模块(使用 Python Tkinter):

如您所见,表单可能具有三种状态:

  • 不可编辑 - 输入框被禁用 - 取消和编辑按钮处于活动状态
  • 无需更改即可编辑 - 输入框处于活动状态,取消按钮和 编辑按钮变为禁用的保存按钮,并且
  • 可通过更改进行编辑 - 输入框处于活动状态,取消按钮变为恢复按钮,保存按钮变为活动状态

我用来实现这个的代码如下:

def display_selected_user(event, user_frame, editable):

    def fill_entry_boxes():
        # Clear entry boxes
        f_name.entry.delete(0, 'end')
        l_name.entry.delete(0, 'end')
        email.entry.delete(0, 'end')

        # Populate with original data
        f_name.entry.insert(0, user_dict[user_id]['f_name'])
        l_name.entry.insert(0, user_dict[user_id]['l_name'])
        email.entry.insert(0, user_dict[user_id]['email'])

        cancel_btn.config(text="Cancel")

    def cancel_revert_process():
        if not changes_made():
            user_detail_frame.grid_forget()
            close_btn.grid(row=2, column=2, padx=10, pady=10, sticky='E')
        else:
            MsgBox = messagebox.askquestion('Revert Changes',
                                            f'Changes have been made - are you sure you want to revert?',
                                            icon='question')
            if MsgBox == 'yes':
                fill_entry_boxes()
                edit_save_btn.config(state='disabled', bg='gray80')

    def save_or_edit(event, _editable):
        if _editable:
            save_process(_editable)
        else:
            make_editable_process(_editable)

    def save_process(_editable):
        # Notify user of process commencement
        user_frame_messenger = MessageBox(user_frame, "")
        user_frame_messenger.grid(row=3, column=0, columnspan=3, sticky='nsew')
        user_frame_messenger.update_content(user_frame, "Saving user changes to database - one moment")
        # Save Process
        changes = record_changes()
        client = MongoClient('mongodb+srv://xxxxxx.zzyri.mongodb.net/test?')
        db = client['football_data']
        collection = db['users']
        for key in changes[1]:
            collection.update_one({"_id": ObjectId(changes[0])},
                                  {"$set": {key: changes[1][key]}}
                                  )
        # Notify user of process completion
        user_frame_messenger.destroy()
        messagebox.showinfo("User detail updated",
                            "The changes to the user details have been saved")
        user_frame.grid_forget()
        cancel_revert_process()
        _editable = False

    def make_editable_process(_editable):

        f_name.entry.config(state='normal')
        l_name.entry.config(state='normal')
        email.entry.config(state='normal')
        _editable = True
        edit_save_btn.config(text="Save", state='disabled', bg='gray80')

    def key_pressed(event):
        if changes_made():
            cancel_btn.config(text="Revert")
            edit_save_btn.config(state='normal', bg="#43E082")
        else:
            cancel_btn.config(text="Cancel")
            edit_save_btn.config(state='disabled', bg='gray80')

    # Record data of selected user
    user = user_lst.tree.focus()
    user_data = user_lst.tree.item(user)
    user_id = user_data['values'][0]

    # Create objects
    f_name = LabelEntryCombo(user_frame, "First name:")
    l_name = LabelEntryCombo(user_frame, "Last name:")
    email = LabelEntryCombo(user_frame, "Email:")
    cancel_btn = ColourSchemedButton(user_frame, "PaleGreen", "Cancel")
    edit_save_btn = ColourSchemedButton(user_frame, "GreenBlue", "Edit", state='normal')

    # Fill entry boxes
    fill_entry_boxes()

    # Bind objects
    f_name.entry.bind('<KeyRelease>', lambda e: key_pressed(e))
    l_name.entry.bind('<KeyRelease>', lambda e: key_pressed(e))
    email.entry.bind('<KeyRelease>', lambda e: key_pressed(e))
    cancel_btn.config(command=cancel_revert_process)
    edit_save_btn.config(command=lambda e=event, d=editable: save_or_edit(e, d))

    # Place and removal of objects
    close_btn.grid_forget()
    user_detail_frame.grid(row=2, columnspan=3, padx=10, pady=10, sticky='NW')
    f_name.frame.grid(row=0, columnspan=3, pady=10, padx=10)
    l_name.frame.grid(row=1, columnspan=3, pady=10, padx=10)
    email.frame.grid(row=2, columnspan=3, pady=10, padx=10)
    cancel_btn.grid(row=3, column=1, pady=10, padx=10)
    edit_save_btn.grid(row=3, column=2, pady=10, padx=10)

    # Configure grid
    user_detail_frame.grid_columnconfigure(0, weight=1)
    user_detail_frame.grid_columnconfigure(1, weight=0)
    user_detail_frame.grid_columnconfigure(2, weight=0)

    # Set state of  objects
    f_name.entry.config(state='disabled')
    l_name.entry.config(state='disabled')
    email.entry.config(state='disabled')

如您所知 - 我使用 lambda 函数将 save_edit_btn 与以下配置绑定,该函数传递用作状态处理程序的 editable 变量 edit_save_btn.config(command=lambda e=event, d=editable: save_or_edit(e, d))

editable 变量在此函数外部初始化为 False,并传递给最顶部的函数 display_selected_user()(如果您愿意,是主程序的回调函数)。因此,当按下save_edit_btn 时,该函数使条目处于活动状态并相应地更改其他小部件(根据函数make_editable_process())并将editable 变量更改为True(即使在它被初始化的主程序中)

问题是,之后该过程返回到 lambda 行并将 editable 变量重置为 False - 我不明白为什么 - 因此永远无法初始化 save_process()

任何关于为什么会这样的想法将不胜感激 - 如果您需要更多代码或详细信息,请告诉我。

【问题讨论】:

  • 这里有很多无法运行的代码供我们查看,但_editable不是global的意思,它的值只会在函数内部相同。因此,除非您将更改后的值传递给新函数,否则该值应该被视为函数内部已经存在的值。或使用global _editable
  • 您对局部变量所做的任何更改,例如 make_editable_process 中的 _editable,仅在该函数内部可见,因此请在外部看到更改,您需要返回它,保存到一些外部给给定可变对象的函数(例如那些各种按钮的东西),或将其声明为globalnonlocal
  • (1) _editable 是局部变量,它不会改变外部变量——即使同名。 (2) 对于像True/False、数字、字符串这样的值,它会将其值发送给函数(或其他变量)——而不是对变量的引用——所以a = False_editable = a_editable = Trueprint(a)a仍然是False,而不是True。为此,您必须使用 ie tkinter.BooleanVar()a = tk.BooleanVar(value=False)_editable = a_editable.set(True)print(a.get())

标签: python tkinter lambda


【解决方案1】:

您对局部变量所做的任何更改,例如 make_editable_process 中的 _editable,仅在该函数内部可见,因此请在外部看到该更改,您需要将其返回,保存到给定可变对象的函数外部(如那些各种按钮的东西),或将其声明为globalnonlocal

对于您的情况,我认为非本地就足够了。

例子

你正在尝试的是这个

>>> def fun():
        a=5
        def f():
            a=42
        print(a)
        f()
        print(a)


>>> fun()
5
5
>>>

a in f 是 f 的局部变量,对它的任何更改仅在内部可见,要在外部进行更改,请执行前面提到的任何方法,例如使用非本地:

>>> def fun2():
        a=5
        def f():
            nonlocal a
            a=42
        print(a)
        f()
        print(a)


>>> fun2()
5
42
>>> 

【讨论】:

    【解决方案2】:

    对于True/False、数字、字符串等值,Python 将其值发送给函数(或其他变量)——而不是对变量的引用。

    例如:

     a = False 
     _editable = a 
     _editable = True
     print(a) 
    

    a 仍然是False,而不是True

    tkinter 中,您将为此使用tkinter.BooleanVar()

     import tkinter as tk
    
     a = tk.BooleanVar(value=False)
     # a.set(False)
     _editable = a 
     _editable.set(True)
     print(a.get())
    

    现在a 会在您更改_editable 时更改值

    请记住使用.get() 来获取值,并使用.set() 而不是= 来设置值。


    编辑:

    在其他情况下,您必须使用 return 并将结果分配回变量

    def function(_editable):
        # ... code ...
    
        _editable = True
    
        return _editable
    
    a = False
    a = function(a)  # assign returned value to `a`
    print(a)
    

    但是当函数被分配给Button(或bind(),或after())时,它无法获得返回值并分配回变量 - 然后通常我们使用global直接赋值到外部变量

    def function(_editable):
        global a
    
        # ... code ...
    
        _editable = True
        a = _editable
    
    
    a = False
    function(a)  # without assignig returned value 
    print(a)
    

    【讨论】:

    • "对于 True/False、数字、字符串等值,Python 将其值发送给函数" 不同类型之间传递的语义没有区别。相同的语法总是会产生相同的语义。
    猜你喜欢
    • 1970-01-01
    • 2016-03-03
    • 2021-03-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 2017-06-13
    • 2019-01-28
    相关资源
    最近更新 更多