PAYATHON 2026

Tkinter Combobox 绑定事件时初始化报 KeyError

排障赵师傅

结论

bind() 的第二个参数需要传入可调用对象,不能在这里直接调用函数。可以用 lambda 接收 Tkinter 传入的事件对象,并把 dictionary 和目标 Label 一起传给 calculate():

self.awg_comb.bind(
    '<<ComboboxSelected>>',
    lambda event: self.calculate(
        event, self.awg_dict, self.awg_uitk_label
    )
)

self.mm2_comb.bind(
    '<<ComboboxSelected>>',
    lambda event: self.calculate(
        event, self.mm2_dict, self.mm2_uitk_label
    )
)

在 calculate() 中通过 event.widget 读取当前 Combobox 的值:

def calculate(self, event, mapping, result_label):
    key = event.widget.get()
    result_label.configure(text=mapping.get(key, ''))

为什么初始化时会出现 KeyError

下面的代码会在执行 bind() 时立即调用 self.calculate():

self.awg_comb.bind(
    '<<ComboboxSelected>>',
    self.calculate(self.awg_dict, self.sel_awg.get())
)

这时用户还没有选择任何内容,所以:

self.sel_awg.get()

返回的是空字符串。随后代码访问:

dict_['']

dictionary 中没有这个 key,因此会抛出 KeyError。

同时,self.calculate(...) 的返回值是 None,这段绑定实际上相当于:

self.awg_comb.bind('<<ComboboxSelected>>', None)

即使没有触发 KeyError,事件绑定也无法正常工作。

可直接使用的完整示例

先创建结果 Label,再绑定事件。回调运行时,目标控件就已经存在。

import tkinter as tk
from tkinter import ttk

options = {'padx': 10, 'pady': 5}
uitk_options = {'borderwidth': 5, 'relief': 'sunken'}


class UnitFrame(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)

        self.awg_dict = {
            '20': '0.50',
            '18': '0.75',
            '16': '1.50',
            '14': '2.50',
            '12': '4.00',
            '10': '6.00',
            '8': '10.00',
            '6': '13.30',
            '4': '21.10',
            '2': '33.60',
            '0': '53.50',
            '00': '67.40',
            '000': '85.00',
            '0000': '107.20',
        }
        self.mm2_dict = {
            value: key for key, value in self.awg_dict.items()
        }

        # AWG to mm2
        ttk.Label(self, text='AWG:').grid(
            column=0, row=0, **options
        )

        self.sel_awg = tk.StringVar()
        self.awg_comb = ttk.Combobox(
            self,
            textvariable=self.sel_awg,
            values=list(self.awg_dict.keys()),
            state='readonly',
        )
        self.awg_comb.grid(column=1, row=0, **options)

        ttk.Label(self, text='MM2:').grid(
            column=0, row=1, **options
        )

        self.awg_uitk_label = ttk.Label(self, **uitk_options)
        self.awg_uitk_label.grid(column=1, row=1, **options)

        # mm2 to AWG
        ttk.Label(self, text='MM2:').grid(
            column=2, row=0, **options
        )

        self.sel_mm2 = tk.StringVar()
        self.mm2_comb = ttk.Combobox(
            self,
            textvariable=self.sel_mm2,
            values=list(self.mm2_dict.keys()),
            state='readonly',
        )
        self.mm2_comb.grid(column=3, row=0, **options)

        ttk.Label(self, text='AWG:').grid(
            column=2, row=1, **options
        )

        self.mm2_uitk_label = ttk.Label(self, **uitk_options)
        self.mm2_uitk_label.grid(column=3, row=1, **options)

        self.awg_comb.bind(
            '<<ComboboxSelected>>',
            lambda event: self.calculate(
                event,
                self.awg_dict,
                self.awg_uitk_label,
            ),
        )

        self.mm2_comb.bind(
            '<<ComboboxSelected>>',
            lambda event: self.calculate(
                event,
                self.mm2_dict,
                self.mm2_uitk_label,
            ),
        )

        self.grid(row=0, column=0, sticky='nsew')

    def calculate(self, event, mapping, result_label):
        key = event.widget.get()
        result_label.configure(text=mapping.get(key, ''))


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Wiresize calculator')
        self.resizable(False, False)


if __name__ == '__main__':
    app = App()
    unit_frame = UnitFrame(app)
    app.mainloop()

回调中的 event 是什么

通过 bind() 注册的回调会收到一个事件对象。对于 <<ComboboxSelected>>,可以通过下面的属性取得触发事件的 Combobox:

event.widget

当前选中的值可以直接这样读取:

key = event.widget.get()

也可以继续读取对应的 StringVar:

self.awg_comb.bind(
    '<<ComboboxSelected>>',
    lambda event: self.calculate(
        self.awg_dict,
        self.sel_awg.get(),
        self.awg_uitk_label,
    ),
)

此时函数可以写成:

def calculate(self, mapping, key, result_label):
    result_label.configure(text=mapping.get(key, ''))

这两种方式都能正常工作。使用 event.widget.get() 后,回调不必额外接收 StringVar,复用起来更方便。

注意事项

当 key 不存在时,dict.get(key, '') 会返回空字符串,从而避免 KeyError。这里的 Combobox 使用了 state='readonly',用户正常选择的内容都来自 values,所以直接使用 mapping[key] 通常也可以。

下面这种方式生成反向 dictionary 时,原 dictionary 中的 value 必须唯一:

self.mm2_dict = {
    value: key for key, value in self.awg_dict.items()
}

如果多个 AWG key 对应同一个 mm2 value,后面的项目会覆盖前面的项目。

备注:内容仅供参考。