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

CustomTkinter实现Excel批量替换时遇RuntimeError: can't re-enter readline错误求助

解决CustomTkinter批量替换Excel内容的RuntimeError问题

问题背景

用CustomTkinter开发简易Excel内容替换工具,Tab1单条文本替换功能正常;Tab2计划实现批量替换,允许用户在文本框输入多行键值对(格式如"AL": "Alabama"),但点击替换按钮时触发RuntimeError: can't re-enter readline错误。

报错原因

GUI程序基于事件循环运行,而input()是命令行交互函数,会尝试启动独立的readline输入循环,与Tkinter的事件循环冲突,导致无法重入readline的错误。此外原代码还存在两个逻辑问题:

  • Tab2错误复用了Tab1的路径输入框self.path,应该用Tab2自己的self.path2
  • 将替换字典转成JSON字符串后调用keys(),JSON字符串没有该方法,无法实现替换匹配

修复方案

  1. 移除所有input()调用,直接从CTkTextbox读取用户输入的多行内容
  2. 解析文本框每行内容,提取键值对(适配"key": "value"格式)
  3. 使用正确的路径输入框self.path2
  4. 直接使用字典作为替换映射,无需转成JSON字符串

修改后的核心代码

修复后的btn2函数

def btn2():
    # 从文本框获取所有内容,按行分割
    text_content = self.textbox.get('0.0', 'end').strip()
    lines = text_content.split('\n')
    
    pairs = {}
    for line in lines:
        line = line.strip()
        if not line:
            continue  # 跳过空行
        try:
            # 处理 "AL": "Alabama" 格式,去掉引号和冒号分割
            key_part, value_part = map(str.strip, line.split(':', 1))
            # 去除前后的引号(支持单/双引号)
            key = key_part.strip('"\'')
            value = value_part.strip('"\'')
            pairs[key] = value
        except ValueError:
            print(f"无效格式行:{line}")
            continue

    if not pairs:
        print("未解析到有效替换键值对")
        return

    BASE_DIR = Path(__file__).parent
    # 使用Tab2的路径输入框
    INPUT_DIR = BASE_DIR / self.path2.get().strip()
    OUTPUT_DIR = BASE_DIR / self.path2.get().strip()

    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    replacement_pair = pairs

    files = list(INPUT_DIR.rglob("*.xls*"))
    for file in files:
        wb = openpyxl.load_workbook(file)
        for ws in wb.worksheets:
            for row in ws.iter_rows():
                for cell in row:
                    if cell.value in replacement_pair:
                        cell.value = replacement_pair[cell.value]
        wb.save(OUTPUT_DIR / f"{file.stem}.xlsx")
    print("批量替换完成")

完整修复后的代码

import tkinter
import tkinter.messagebox
import customtkinter
from pathlib import Path
import openpyxl

customtkinter.set_appearance_mode("System")
customtkinter.set_default_color_theme("blue")


class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        def btn1():
            BASE_DIR = Path(__file__).parent
            INPUT_DIR = BASE_DIR / self.path.get().strip()
            OUTPUT_DIR = BASE_DIR / self.path.get().strip()

            OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

            replacement_pair = {
                self.find.get().strip(): self.repl.get().strip()
            }

            files = list(INPUT_DIR.rglob("*.xls*"))
            for file in files:
                wb = openpyxl.load_workbook(file)
                for ws in wb.worksheets:
                    for row in ws.iter_rows():
                        for cell in row:
                            if cell.value in replacement_pair:
                                cell.value = replacement_pair[cell.value]
                wb.save(OUTPUT_DIR / f"{file.stem}.xlsx")
            print("单条替换完成")

        def btn2():
            text_content = self.textbox.get('0.0', 'end').strip()
            lines = text_content.split('\n')
            
            pairs = {}
            for line in lines:
                line = line.strip()
                if not line:
                    continue
                try:
                    key_part, value_part = map(str.strip, line.split(':', 1))
                    key = key_part.strip('"\'')
                    value = value_part.strip('"\'')
                    pairs[key] = value
                except ValueError:
                    print(f"无效格式行:{line}")
                    continue

            if not pairs:
                print("未解析到有效替换键值对")
                return

            BASE_DIR = Path(__file__).parent
            INPUT_DIR = BASE_DIR / self.path2.get().strip()
            OUTPUT_DIR = BASE_DIR / self.path2.get().strip()

            OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

            replacement_pair = pairs

            files = list(INPUT_DIR.rglob("*.xls*"))
            for file in files:
                wb = openpyxl.load_workbook(file)
                for ws in wb.worksheets:
                    for row in ws.iter_rows():
                        for cell in row:
                            if cell.value in replacement_pair:
                                cell.value = replacement_pair[cell.value]
                wb.save(OUTPUT_DIR / f"{file.stem}.xlsx")
            print("批量替换完成")

        # 窗口配置
        self.title("Find and Replace")
        self.geometry(f"{500}x{525}")

        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure((2, 3), weight=0)
        self.grid_rowconfigure((0, 1, 2), weight=1)

        # 标签页设置
        self.tabview = customtkinter.CTkTabview(self, width=250)
        self.tabview.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew")
        self.tabview.add("Tab 1")
        self.tabview.add("Tab 2")
        self.tabview.add("Tab 3")
        self.tabview.tab("Tab 1").grid_columnconfigure(0, weight=1)
        self.tabview.tab("Tab 2").grid_columnconfigure(0, weight=1)
        self.tabview.tab("Tab 3").grid_columnconfigure(0, weight=1)

        # Tab1 单条替换控件
        self.find = customtkinter.CTkEntry(self.tabview.tab("Tab 1"), placeholder_text="Find")
        self.find.grid(row=0, column=0, padx=20, pady=(20, 10))
       
        self.repl = customtkinter.CTkEntry(self.tabview.tab("Tab 1"), placeholder_text="Replace")
        self.repl.grid(row=1, column=0, padx=20, pady=(20, 10))
        
        self.path = customtkinter.CTkEntry(self.tabview.tab("Tab 1"), placeholder_text="Path")
        self.path.grid(row=2, column=0, padx=20, pady=(20, 10))

        self.button = customtkinter.CTkButton(self.tabview.tab("Tab 1"), text="Replace", command=btn1)
        self.button.grid(row=3, column=0, padx=20, pady=(20, 10))
        
        # Tab2 批量替换控件
        self.textbox = customtkinter.CTkTextbox(self.tabview.tab("Tab 2"), width=250)
        self.textbox.grid(row=0, column=0, padx=(5, 5), pady=(0, 0), sticky="nsew")

        self.path2 = customtkinter.CTkEntry(self.tabview.tab("Tab 2"), placeholder_text="Path")
        self.path2.grid(row=2, column=0, padx=20, pady=(20, 10))

        self.button2 = customtkinter.CTkButton(self.tabview.tab("Tab 2"), text="Replace", command=btn2)
        self.button2.grid(row=3, column=0, padx=20, pady=(20, 10))
        
    def open_input_dialog_event(self):
        dialog = customtkinter.CTkInputDialog(text="Type in a number:", title="CTkInputDialog")
        print("CTkInputDialog:", dialog.get_input())

    def change_appearance_mode_event(self, new_appearance_mode: str):
        customtkinter.set_appearance_mode(new_appearance_mode)

    def change_scaling_event(self, new_scaling: str):
        new_scaling_float = int(new_scaling.replace("%", "")) / 100
        customtkinter.set_widget_scaling(new_scaling_float)

    def sidebar_button_event(self):
        print("sidebar_button click")


if __name__ == "__main__":
    app = App()
    app.mainloop()

使用说明

  1. 在Tab2的文本框中输入多行键值对,每行格式为"原内容": "替换内容",例如:
    "AL": "Alabama"
    "NY": "New York"
    "CA": "California"
    
  2. 输入目标文件夹路径
  3. 点击替换按钮,程序会批量处理文件夹下的所有Excel文件,完成内容替换

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 19:18:11