You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter递归函数compare_stages()调用触发NoneType报错问题

问题根因

报错由三个代码逻辑错误直接导致:

  1. 递归调用无返回值:compare_stages()、compare_doc_lists()中触发递归重选逻辑时,仅调用了递归函数本身,未加return关键字透传返回值,导致进入递归分支时最外层调用拿到的返回值为默认的None,后续访问stage[0]时就会触发NoneType下标访问错误。
  2. Tk多实例冲突:所有选择、提示逻辑都新建独立的Tk()根窗口,旧窗口未销毁,会导致Tkinter事件循环混乱、变量取值异常。
  3. 选择函数取值逻辑错误:select_doc/select_stage/select_reviewer中绑定在OptionMenu上的save_selected回调仅在点击下拉选项时触发,mainloop()结束后手动传入StringVar对象调用该函数,本身无法拿到正确的用户选择值。
修复方案
  • 所有递归调用前加return,把内层递归的合法返回值逐层透传到最外层调用
  • 全局只保留一个Tk()根窗口,所有弹窗用Toplevel实现,用完及时销毁,避免多实例冲突
  • 修正选择类函数的取值逻辑,增加确认按钮,用wait_window()等用户操作完成、窗口销毁后再读取选择值
  • 审核人已完成阶段用字典统一存储,替换多组独立列表+重复if判断的冗余写法
  • 抽离通用提示弹窗函数,减少重复代码
修正后可运行代码
from tkinter import *

# 基础配置数据
doc_list = ["doc_A","doc_B","doc_C","doc_D"]
review_stages = ["internal_review","client_review","reg_review"]
reviewer_list = ["Sue","Joe","Don","Billy"]
doc_reviews_completed = ["doc_B"]
review_stages_completed = ["internal_review"]
# 字典存储审核人已完成阶段,替代多个独立列表
reviewer_completed_map = {
    "Sue": ["client_review"],
    "Joe": [],
    "Don": [],
    "Billy": []
}

def show_tip(msg):
    """通用提示弹窗"""
    top = Toplevel()
    top.geometry("250x170")
    Label(top, text="Oops!", font=("Courier",14)).pack()
    txt = Text(top, height=5, width=40)
    txt.pack()
    txt.insert(END, msg)
    Button(top, text="确定", command=top.destroy).pack()
    top.wait_window() # 等待弹窗关闭后再执行后续逻辑

def select_doc(root):
    """选择文档,基于主窗口生成子弹窗"""
    select_win = Toplevel(root)
    select_win.geometry("715x250")
    select_win.grab_set() # 模态窗口,必须完成当前选择才能操作其他窗口
    selected = StringVar(value="Select Document")
    
    def on_confirm():
        select_win.destroy()
    
    OptionMenu(select_win, selected, *doc_list).pack()
    Button(select_win, text="确认选择", command=on_confirm).pack()
    select_win.wait_window()
    return selected.get()

def select_reviewer(root):
    """选择审核人"""
    select_win = Toplevel(root)
    select_win.geometry("715x250")
    select_win.grab_set()
    selected = StringVar(value="Select reviewer name.")
    
    def on_confirm():
        select_win.destroy()
    
    OptionMenu(select_win, selected, *reviewer_list).pack()
    Button(select_win, text="确认选择", command=on_confirm).pack()
    select_win.wait_window()
    return selected.get()

def select_stage(root):
    """选择审核阶段"""
    select_win = Toplevel(root)
    select_win.geometry("715x250")
    select_win.grab_set()
    selected = StringVar(value="Select review stage.")
    
    def on_confirm():
        select_win.destroy()
    
    OptionMenu(select_win, selected, *review_stages).pack()
    Button(select_win, text="确认选择", command=on_confirm).pack()
    select_win.wait_window()
    return selected.get()

def compare_doc_lists(root):
    a = select_doc(root)
    if a in doc_reviews_completed:
        show_tip("Document review complete. Please choose again.")
        # 递归前加return透传返回值
        return compare_doc_lists(root)
    else:
        return a

def compare_stages(root):
    stage_choice = select_stage(root)
    reviewer = select_reviewer(root)
    stage_list = reviewer_completed_map[reviewer]

    if stage_choice in review_stages_completed:
        show_tip("The selected stage is complete for this document. Please choose again.")
        # 递归前加return透传返回值
        return compare_stages(root)
    elif stage_choice in stage_list:
        show_tip("You have already completed this review stage. Please choose again.")
        # 递归前加return透传返回值
        return compare_stages(root)
    else:
        return stage_choice, reviewer
    
def enter_stage_completed():
    # 全局唯一主根窗口,所有弹窗基于该窗口生成
    root = Tk()
    root.withdraw() # 隐藏默认主窗口,仅展示功能弹窗
    stage = compare_stages(root)
       
    confirm_win = Toplevel(root)
    confirm_win.geometry("300x100")
    confirm_win.grab_set()
    selected_stage = stage[0]
    
    def save_selected():
        confirm_win.destroy()
        return selected_stage
       
    Label(confirm_win, text="Click to confirm that you completed reviewing the previously selected review stage.").pack()   
    btn = Button(
        master = confirm_win,
        text=selected_stage,
        command=save_selected
        )
    btn.pack()
    confirm_win.wait_window()
    root.destroy() # 所有流程结束销毁主窗口
    return selected_stage

# 功能测试
if __name__ == "__main__":
    res = enter_stage_completed()
    print("用户确认完成的阶段:", res)

内容的提问来源于stack exchange,提问作者mountainscaler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:01:14