本文实现一个Windows控制台交互工具,聚焦Win10+原生支持的ANSI转义序列和UTF-8编码,实现专业级控制台输出能力,涵盖编码管理、彩色输出、表格渲染、光标控制等核心功能,最终实现一个可直接复用的Python控制台工具类。
一、目标与核心功能清单
| |
|---|
| 初始化控制台为UTF-8编码、程序退出时恢复原始编码、兼容中文/特殊字符输出 |
| 支持14种颜色(7种基础色+7种亮色)、样式重置、快捷输出(成功/错误/警告/信息) |
| 带圆角边框、自定义列宽、单元格独立着色、表头/数据/表尾分层渲染 |
| 光标定位/隐藏/保存恢复、清屏/清行、进度条(动态刷新)、带边框文本块 |
| |
二、技术原理
- Windows UTF-8控制台配置:通过
ctypes调用Windows API设置控制台代码页(CP_UTF8)、启用虚拟终端处理(支持ANSI转义序列); - ANSI转义序列:使用
\033[xxxm格式实现颜色、样式、光标控制,Win10+原生支持; - 结构化输出:通过字符串格式化、列宽计算实现表格/边框文本的规整渲染;
- 资源管理:初始化时保存控制台原始状态,退出时恢复,避免影响系统环境。
三、项目开发步骤
1. 项目结构与基础环境准备
创建项目文件结构:
console_tool/├── console_utils.py # 核心工具类└── demo.py # 功能演示脚本
2. 核心工具类基础架构
首先实现控制台UTF-8初始化与恢复,这是所有功能的基础(依赖ctypes调用Windows API)。
# console_utils.pyimport ctypesimport osimport sysfrom ctypes import wintypesfrom typing import Dict, List, Optional# Windows API 常量定义STD_OUTPUT_HANDLE = -11ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004CP_UTF8 = 65001# 加载Windows APIkernel32 = ctypes.WinDLL("kernel32", use_last_error=True)# 类型定义HANDLE = wintypes.HANDLEDWORD = wintypes.DWORDUINT = wintypes.UINTWORD = wintypes.WORDSHORT = wintypes.SHORTSMALL_RECT = wintypes.SMALL_RECT# 定义 CONSOLE_SCREEN_BUFFER_INFO 结构体classCONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): _fields_ = [ ("dwSize", wintypes._COORD), ("dwCursorPosition", wintypes._COORD), ("wAttributes", WORD), ("srWindow", SMALL_RECT), ("dwMaximumWindowSize", wintypes._COORD), ]# API函数原型kernel32.GetStdHandle.argtypes = [DWORD]kernel32.GetStdHandle.restype = HANDLEkernel32.GetConsoleMode.argtypes = [HANDLE, ctypes.POINTER(DWORD)]kernel32.GetConsoleMode.restype = wintypes.BOOLkernel32.SetConsoleMode.argtypes = [HANDLE, DWORD]kernel32.SetConsoleMode.restype = wintypes.BOOLkernel32.SetConsoleOutputCP.argtypes = [UINT]kernel32.SetConsoleOutputCP.restype = wintypes.BOOLkernel32.SetConsoleCP.argtypes = [UINT]kernel32.SetConsoleCP.restype = wintypes.BOOLkernel32.GetConsoleOutputCP.argtypes = []kernel32.GetConsoleOutputCP.restype = UINTkernel32.GetConsoleCP.argtypes = []kernel32.GetConsoleCP.restype = UINTkernel32.GetConsoleScreenBufferInfo.argtypes = [ HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),]kernel32.GetConsoleScreenBufferInfo.restype = wintypes.BOOLclassConsoleUtils:"""Windows控制台高级交互工具类(Win10+)"""# 静态变量保存控制台原始状态 _original_output_cp: UINT = 0 _original_input_cp: UINT = 0 _original_console_mode: DWORD = 0 _is_initialized: bool = False# ANSI转义序列常量(核心) ANSI: Dict[str, str] = {# 基础重置/样式"RESET": "\033[0m","BOLD": "\033[1m",# 前景色(7基础+7亮色)"FG_RED": "\033[31m","FG_GREEN": "\033[32m","FG_YELLOW": "\033[33m","FG_BLUE": "\033[34m","FG_MAGENTA": "\033[35m","FG_CYAN": "\033[36m","FG_WHITE": "\033[37m","FG_BRIGHT_RED": "\033[91m","FG_BRIGHT_GREEN": "\033[92m","FG_BRIGHT_YELLOW": "\033[93m","FG_BRIGHT_BLUE": "\033[94m","FG_BRIGHT_MAGENTA": "\033[95m","FG_BRIGHT_CYAN": "\033[96m","FG_BRIGHT_WHITE": "\033[97m",# 背景色(用于进度条)"BG_GREEN": "\033[42m","BG_BRIGHT_BLACK": "\033[100m",# 光标控制"CLEAR_SCREEN": "\033[2J","CLEAR_LINE": "\033[2K","CURSOR_HOME": "\033[H","CURSOR_HIDE": "\033[?25l","CURSOR_SHOW": "\033[?25h","SAVE_CURSOR": "\033[s","RESTORE_CURSOR": "\033[u", }# 颜色代码映射(1-14对应需求的14种颜色) COLOR_MAP: Dict[int, str] = {1: ANSI["FG_RED"],2: ANSI["FG_GREEN"],3: ANSI["FG_YELLOW"],4: ANSI["FG_BLUE"],5: ANSI["FG_MAGENTA"],6: ANSI["FG_CYAN"],7: ANSI["FG_WHITE"],8: ANSI["FG_BRIGHT_RED"],9: ANSI["FG_BRIGHT_GREEN"],10: ANSI["FG_BRIGHT_YELLOW"],11: ANSI["FG_BRIGHT_BLUE"],12: ANSI["FG_BRIGHT_MAGENTA"],13: ANSI["FG_BRIGHT_CYAN"],14: ANSI["FG_BRIGHT_WHITE"], } @classmethoddefinit_utf8_console(cls) -> bool:"""初始化控制台为UTF-8模式,启用虚拟终端支持"""if cls._is_initialized:returnTruetry:# 1. 获取标准输出句柄 h_std_out = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)if h_std_out == wintypes.HANDLE(-1):raise OSError("获取控制台句柄失败")# 2. 保存原始控制台状态 cls._original_output_cp = kernel32.GetConsoleOutputCP() cls._original_input_cp = kernel32.GetConsoleCP() original_mode = DWORD(0) kernel32.GetConsoleMode(h_std_out, ctypes.byref(original_mode)) cls._original_console_mode = original_mode.value# 3. 设置UTF-8编码ifnot kernel32.SetConsoleOutputCP(CP_UTF8):raise OSError("设置输出编码为UTF-8失败")ifnot kernel32.SetConsoleCP(CP_UTF8): kernel32.SetConsoleOutputCP(cls._original_output_cp)raise OSError("设置输入编码为UTF-8失败")# 4. 启用虚拟终端处理(支持ANSI转义序列) new_mode = cls._original_console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSINGifnot kernel32.SetConsoleMode(h_std_out, DWORD(new_mode)):# 降级启用基础处理模式 new_mode = ( cls._original_console_mode | 0x0001 ) # ENABLE_PROCESSED_OUTPUT kernel32.SetConsoleMode(h_std_out, DWORD(new_mode))# 5. 设置Python标准输出编码if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") cls._is_initialized = TruereturnTrueexcept Exception as e: print(f"控制台初始化失败: {e}", file=sys.stderr)returnFalse
3. 实现彩色UTF-8文本输出功能
在ConsoleUtils类中新增彩色输出相关方法,支持14种颜色映射和快捷消息输出:
@classmethoddefprint_utf8(cls, text: str, end: str = "\n") -> None:"""打印UTF-8编码的纯文本"""ifnot cls._is_initialized andnot cls.init_utf8_console(): print(text, end=end)return print(text, end=end) @classmethoddefprint_color_utf8(cls, text: str, color_code: int = 7, end: str = "\n") -> None:""" 打印带颜色的UTF-8文本 :param text: 输出文本 :param color_code: 颜色代码(1-14,对应COLOR_MAP) :param end: 结束符 """ifnot cls._is_initialized andnot cls.init_utf8_console(): print(text, end=end)return# 获取颜色ANSI码,默认白色 color_ansi = cls.COLOR_MAP.get(color_code, cls.ANSI["FG_WHITE"]) print(f"{color_ansi}{text}{cls.ANSI['RESET']}", end=end) @classmethoddefprint_success(cls, text: str) -> None:"""打印成功消息(亮绿色)""" cls.print_color_utf8(f"{text}", 9) @classmethoddefprint_error(cls, text: str) -> None:"""打印错误消息(亮红色)""" cls.print_color_utf8(f"{text}", 8) @classmethoddefprint_warning(cls, text: str) -> None:"""打印警告消息(亮黄色)""" cls.print_color_utf8(f"{text}", 10) @classmethoddefprint_info(cls, text: str) -> None:"""打印信息消息(亮青色)""" cls.print_color_utf8(f"{text}", 13)
4. 实现结构化表格输出功能
新增表格渲染方法,支持自定义列宽、单元格着色、圆角边框:
@classmethoddef_get_col_widths(cls, headers: List[str], widths: List[int]) -> List[int]:"""计算列宽(默认15,不足则用表头长度补充)""" col_widths = []for i, header in enumerate(headers):if i < len(widths) and widths[i] > 0: col_widths.append(widths[i])else: col_widths.append(max(15, len(header)))return col_widths @classmethoddefprint_table_header(cls, headers: List[str], widths: List[int] = []) -> None:"""打印表格表头(带顶部边框)"""ifnot headers:returnifnot cls._is_initialized andnot cls.init_utf8_console():return col_widths = cls._get_col_widths(headers, widths)# 打印顶部边框 border = cls.ANSI["BOLD"] + cls.ANSI["FG_BRIGHT_BLUE"] border += "┌" + "┌".join(["─" * w for w in col_widths]) + "┐" print(border)# 打印表头内容 header_line = cls.ANSI["BOLD"] + cls.ANSI["FG_BRIGHT_BLUE"] + "│"for i, header in enumerate(headers): header_line += f"{header.ljust(col_widths[i])}│" print(header_line + cls.ANSI["RESET"])# 打印分隔线 sep_line = cls.ANSI["BOLD"] + cls.ANSI["FG_BRIGHT_BLUE"] + "├" sep_line += "┼".join(["─" * w for w in col_widths]) + "┤" print(sep_line + cls.ANSI["RESET"]) @classmethoddefprint_table_row(cls, cells: List[str], widths: List[int] = [], colors: List[int] = []) -> None:"""打印表格行(支持单元格独立着色)"""ifnot cells:returnifnot cls._is_initialized andnot cls.init_utf8_console():return col_widths = cls._get_col_widths(cells, widths) row_line = "│"for i, cell in enumerate(cells): cell_text = cell.ljust(col_widths[i])[:col_widths[i]] # 截断超长文本if i < len(colors):# 单元格着色 row_line += f"{cls.COLOR_MAP.get(colors[i], cls.ANSI['FG_WHITE'])}{cell_text}{cls.ANSI['RESET']}│"else: row_line += f"{cell_text}│" print(row_line) @classmethoddefprint_table_footer(cls, widths: List[int]) -> None:"""打印表格底部边框"""ifnot widths:returnifnot cls._is_initialized andnot cls.init_utf8_console():return footer_line = cls.ANSI["BOLD"] + cls.ANSI["FG_BRIGHT_BLUE"] + "└" footer_line += "┴".join(["─" * w for w in widths]) + "┘" print(footer_line + cls.ANSI["RESET"])
5. 实现光标控制/进度条/边框文本等高级功能
@classmethoddefget_console_size(cls) -> tuple[int, int]:"""获取控制台尺寸(宽度, 高度)"""try: h_std_out = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) csbi = wintypes.CONSOLE_SCREEN_BUFFER_INFO() kernel32.GetConsoleScreenBufferInfo(h_std_out, ctypes.byref(csbi)) width = csbi.srWindow.Right - csbi.srWindow.Left + 1 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1return width, heightexcept:return80, 25# 默认值 @classmethoddefclear_screen(cls) -> None:"""清空屏幕并将光标移至首页"""ifnot cls._is_initialized andnot cls.init_utf8_console(): os.system("cls")return print(f"{cls.ANSI['CLEAR_SCREEN']}{cls.ANSI['CURSOR_HOME']}", end="") @classmethoddefclear_line(cls) -> None:"""清空当前行并将光标移至行首"""if cls._is_initialized or cls.init_utf8_console(): print(f"{cls.ANSI['CLEAR_LINE']}\r", end="") @classmethoddefset_cursor_position(cls, row: int, col: int) -> None:"""设置光标位置(1-based)"""ifnot cls._is_initialized andnot cls.init_utf8_console():return print(f"\033[{row};{col}H", end="") @classmethoddefshow_cursor(cls, show: bool = True) -> None:"""显示/隐藏光标"""ifnot cls._is_initialized andnot cls.init_utf8_console():return print(cls.ANSI["CURSOR_SHOW"] if show else cls.ANSI["CURSOR_HIDE"], end="") @classmethoddefsave_cursor(cls) -> None:"""保存光标位置"""if cls._is_initialized or cls.init_utf8_console(): print(cls.ANSI["SAVE_CURSOR"], end="") @classmethoddefrestore_cursor(cls) -> None:"""恢复光标位置"""if cls._is_initialized or cls.init_utf8_console(): print(cls.ANSI["RESTORE_CURSOR"], end="") @classmethoddefprint_progress_bar(cls, percentage: int, width: int = -1, label: str = "进度") -> None:""" 打印动态进度条 :param percentage: 进度百分比(0-100) :param width: 进度条宽度(-1=自动适配) :param label: 进度条标签 """ifnot cls._is_initialized andnot cls.init_utf8_console(): print(f"\r{label}: {percentage}%", end="")if percentage >= 100: print()return# 自动计算宽度if width <= 0: console_width, _ = cls.get_console_size() width = console_width - len(label) - 10# 预留标签和百分比空间 width = max(10, width) # 最小宽度# 计算进度条分段 percentage = max(0, min(100, percentage)) completed = int(width * percentage / 100) remaining = width - completed# 构建进度条 bar = f"[{cls.ANSI['BG_GREEN']}{' ' * completed}{cls.ANSI['BG_BRIGHT_BLACK']}{' ' * remaining}{cls.ANSI['RESET']}] {percentage}%"# 动态刷新 print(f"\r{label}: {bar}", end="")if percentage >= 100: print() @classmethoddefprint_boxed_text(cls, text: str, box_color: int = 6, text_color: int = 14) -> None:""" 打印带边框的文本块 :param text: 文本内容(支持换行) :param box_color: 边框颜色代码 :param text_color: 文本颜色代码 """ifnot cls._is_initialized andnot cls.init_utf8_console(): print(text)return# 分割文本行并计算最大长度 lines = text.split("\n") max_len = max(len(line) for line in lines) if lines else0# 打印边框和文本 box_ansi = cls.COLOR_MAP.get(box_color, cls.ANSI["FG_CYAN"])# 顶部边框 print(f"{box_ansi}┌{'─' * (max_len + 2)}┐{cls.ANSI['RESET']}")# 文本行for line in lines: padded_line = line.ljust(max_len) print(f"{box_ansi}│ {cls.COLOR_MAP.get(text_color, cls.ANSI['FG_BRIGHT_WHITE'])}{padded_line}{cls.ANSI['RESET']}{box_ansi} │{cls.ANSI['RESET']}")# 底部边框 print(f"{box_ansi}└{'─' * (max_len + 2)}┘{cls.ANSI['RESET']}") @classmethoddefget_console_info(cls) -> Dict[str, str]:"""获取控制台信息(编码、尺寸、虚拟终端支持)""" info = {}# 编码信息 info["输出编码"] = "UTF-8"if kernel32.GetConsoleOutputCP() == CP_UTF8 elsef"代码页{kernel32.GetConsoleOutputCP()}" info["输入编码"] = "UTF-8"if kernel32.GetConsoleCP() == CP_UTF8 elsef"代码页{kernel32.GetConsoleCP()}"# 尺寸信息 width, height = cls.get_console_size() info["控制台尺寸"] = f"{width}x{height}"# 虚拟终端支持try: h_std_out = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) mode = DWORD(0) kernel32.GetConsoleMode(h_std_out, ctypes.byref(mode)) info["虚拟终端支持"] = "是"if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) else"否"except: info["虚拟终端支持"] = "未知"return info
6. 编写演示脚本
创建demo.py,测试工具类的全部核心功能:
# demo.pyimport timefrom console_utils import ConsoleUtilsdefmain():# 1. 初始化控制台ifnot ConsoleUtils.init_utf8_console():return1try:# ========== 功能1:基础彩色输出 ========== ConsoleUtils.print_color_utf8("=== Python控制台工具演示 ===\n", 9) ConsoleUtils.print_success("初始化成功:控制台已切换为UTF-8编码") ConsoleUtils.print_info("支持中文、特殊字符、ANSI颜色输出") ConsoleUtils.print_warning("这是一条警告消息(亮黄色)") ConsoleUtils.print_error("这是一条错误消息(亮红色)\n")# ========== 功能2:控制台信息检测 ========== ConsoleUtils.print_color_utf8("控制台信息检测\n", 11) console_info = ConsoleUtils.get_console_info()for key, value in console_info.items(): ConsoleUtils.print_utf8(f" {key}: {value}") print()# ========== 功能3:结构化表格输出 ========== ConsoleUtils.print_color_utf8("技术指标状态表格\n", 10) headers = ["指标名称", "状态", "周期"] widths = [12, 8, 8]# 打印表头 ConsoleUtils.print_table_header(headers, widths)# 打印行(带单元格着色) ConsoleUtils.print_table_row(["移动平均(MA)", "正常", "20日"], widths, [14, 9, 14]) ConsoleUtils.print_table_row(["MACD", "正常", "12/26/9"], widths, [14, 9, 14]) ConsoleUtils.print_table_row(["KDJ", "警告", "9日"], widths, [14, 10, 14]) ConsoleUtils.print_table_row(["RSI", "错误", "14日"], widths, [14, 8, 14])# 打印表尾 ConsoleUtils.print_table_footer(widths) print()# ========== 功能4:进度条演示 ========== ConsoleUtils.print_color_utf8("进度条演示\n", 11)for i in range(0, 101, 5): ConsoleUtils.print_progress_bar(i, label="下载进度") time.sleep(0.1) print()# ========== 功能5:边框文本演示 ========== ConsoleUtils.print_color_utf8("带边框文本块\n", 12) box_text = """通达信Python插件控制台工具支持功能:1. UTF-8编码管理2. 14色彩色输出3. 结构化表格渲染4. 光标控制与进度条5. 控制台信息检测""" ConsoleUtils.print_boxed_text(box_text, box_color=6, text_color=14) print()# ========== 功能6:光标控制演示 ========== ConsoleUtils.print_color_utf8("光标控制演示\n", 13) ConsoleUtils.save_cursor() # 保存光标位置 ConsoleUtils.set_cursor_position(ConsoleUtils.get_console_size()[1]-2, 10) # 移至倒数第2行第10列 ConsoleUtils.print_color_utf8("光标定位测试", 8) time.sleep(1) ConsoleUtils.restore_cursor() # 恢复光标位置 ConsoleUtils.show_cursor(False) # 隐藏光标 time.sleep(1) ConsoleUtils.show_cursor(True) # 显示光标 print()finally:# 必须恢复控制台设置 ConsoleUtils.restore_console() ConsoleUtils.print_success("演示结束,控制台已恢复原始设置")if __name__ == "__main__": main()
四、项目运行与验证
python demo.py
效果如下:
2. 验证要点:
五、后续待扩展
- 异常处理增强:为所有API调用添加更精细的错误捕获;
- 样式扩展:增加背景色、加粗/下划线等复合样式支持;
- 日志集成:对接Python标准logging模块,实现彩色日志输出。
六、核心知识点总结
- Windows API调用:通过
ctypes操作控制台句柄、代码页、终端模式; - ANSI转义序列:掌握颜色、样式、光标控制的核心转义码格式;
- UTF-8编码管理:解决Windows控制台中文乱码的核心方案;
- 结构化输出:通过字符串格式化实现规整的表格/边框渲染;
- 资源管理:初始化-使用-恢复的生命周期管理模式,避免环境污染。
本文实现的工具类可直接集成到其他软件,如自动化脚本、命令行工具等场景,完全适配Windows 10+系统,具备专业级的控制台交互能力。