命令行打印输出字符缓慢之谜"

admin 2026-10-16 11:42:34 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

命令行打印输出字符缓慢之谜

原创

微慑网
微慑网

微慑网

2026年9月20日 13:46
广东

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

问题描述:

之前写了一个脚本,但不知道从什么时候开始,使用python38/python39,在脚本运行一段时间后,输出字符很慢,一个个蹦出来似的,无论是cmd/cmder/powershell/Tabby都会这样;但是脚本在笔记本上运行又是正常的,台式机上却都有这个情况;

之前查过文章问过AI 都没结果,一直想着是电脑环境的问题;最近让AI直接检查了代码,发现是引用了colorama的锅,这个模块是后来加的,用来做终端彩色输出;在这之前使用的是termcolor,但不如colorama方便;

问题关键:

问题出在colorama的0.4.6的版本的init()没有幂等保护,存在多种问题,

参考:

https://data.safetycli.com/packages/pypi/colorama/changelog?page=1&

而我在for循环中,还多次调用了init();

另外,以下是AI给出的一个检测代码:

import sysfrom colorama import ansitowin32c = ansitowin32.AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=True)print(\"convert =\", c.convert, \"  strip =\", c.strip, \"  should_wrap =\", c.should_wrap())

台式机:

笔记本:

区别仅仅是strip的值,AI的解释是:

当 strip=False 时,你的输出里充满了类似 \\x1b[32m、\\x1b[0m 这样的控制序列。这些序列会被原样送到终端,终端必须逐个解析、渲染颜色和样式。在 Windows 控制台(即使是现代 Windows Terminal 的兼容层)或某些 IDE 内置终端中,处理这些序列的代价可能非常高。
而 strip=True 时,终端收到的几乎是纯文本。虽然 Python 多花了一点时间做正则匹配,但终端侧的负担被大幅卸掉了。两者相抵,你感知到的“输出变慢”就消失了。

解决问题:

(1)停止多次引用init(),直接在main使用一次init(autoreset=True)

(2)使用colorama.just\_fix\_windows\_console() ;可以多次引用,其内部有幂等保护,重复调用不会叠加层数

修复之前的代码:

sys.stdout.reconfigure(line_buffering=False, write_through=True)sys.stdout.reconfigure(line_buffering=True)
for  ******:if match_content in List1 or checkTitle:
                        init(autoreset=True)                        print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        doChoice()                    elif match_content in List:
                        init(autoreset=True)                        print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        doChoice()                    else:                        init(autoreset=True)                        print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        print(Style.RESET_ALL)                        print ('\\t+-------------------------------------------------------------+\\r')                        doChoice()def main):  *************************

修复之后

#sys.stdout.reconfigure(line_buffering=False, write_through=True)#sys.stdout.reconfigure(line_buffering=True)for  ******:if match_content in List1 or checkTitle:                        print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        doChoice()                    elif match_content in List:                                                print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        doChoice()                    else:                                                print('\\t|-> ***:',Fore.GREEN+match_content+Style.RESET_ALL,Back.RED +alertWord+Style.RESET_ALL,flush=True)                        print(Style.RESET_ALL)                        print ('\\t+-------------------------------------------------------------+\\r')                        doChoice()def main):  init(autoreset=True)  *************************

脚本复现测试结果:

================================================================                                                                                                                                                    colorama 输出卡顿:复现与验证工具                                                                                                                                                                                             ================================================================                                                                                                                                                                                                                                                                                                                                                                      [1] 当前环境检测                                                                                                                                                                                                             isatty        = True                                                                                                                                                                                               convert       = False                                                                                                                                                                                              strip         = False                                                                                                                                                                                              should_wrap   = True                                                                                                                                                                                               判定          : 指数爆炸:convert=False 且 strip=False → reset_all() 写文本并递归 → 会卡                                                                                                                                           当前嵌套层数  : 0                                                                                                                                                                                                                                                                                                                                                                                                                       [2] 计数模式(不刷屏,精确统计底层写入次数)                                                                                                                                                                                                                                                                                                                                                                                                                【指数路径 · 台式机 Cmder 属于此类】convert=False  strip=False                                                                                                                                                                  文章数 |  修复前(累计) | 修复前(单篇峰值) | 修复后(累计)                                                                                                                                                                               -------+--------------+------------------+-------------                                                                                                                                                                 5 |            62 |              32 |           10                                                                                                                                                                10 |          2046 |            1024 |           20                                                                                                                                                                15 |         65534 |           32768 |           30                                                                                                                                                                18 |        524286 |          262144 |           36                                                                                                                                                                20 |       2097150 |         1048576 |           40                                                                                                                                                                                                                                                                                                                                                                               【短路路径 · 非 tty / 重定向属于此类】convert=False  strip=True                                                                                                                                                                  文章数 |  修复前(累计) | 修复前(单篇峰值) | 修复后(累计)                                                                                                                                                                               -------+--------------+------------------+-------------                                                                                                                                                                 5 |             5 |               1 |            5                                                                                                                                                                10 |            10 |               1 |           10                                                                                                                                                                15 |            15 |               1 |           15                                                                                                                                                                18 |            18 |               1 |           18                                                                                                                                                                20 |            20 |               1 |           20                                                                                                                                                                                                                                                                                                                                                                               怎么读这两张表:                                                                                                                                                                                                             · 指数路径:修复前单篇峰值 = 2^N,20 篇累计 209 万次写入,                                                                                                                                                                                修复后只有 40 次 —— 差 5 万倍,这就是卡顿的来源。                                                                                                                                                                                   · 短路路径:修复前后完全持平,每篇恒定 1 次写入。                                                                                                                                                                                          层数照样在涨,却多不出任何一次写入 —— 这就是该环境下                                                                                                                                                                                       bug 不显形的直接证据。                                                                                                                                                                                                                                                                                                                                                                                                                   ==============================================================                                                                                                                                                      真实输出演示:每\"篇\"调用一次 init(autoreset=True),再输出一行                                                                                                                                                                         单次耗时超过 3.0 秒自动停止,也可随时按 Ctrl+C 中断                                                                                                                                                                                  ==============================================================                                                                                                                                                                                                                                                                                                                                                                        下面的彩色行走的是被嵌套后的 sys.stdout:                                                                                                                                                                                         [第 1 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 1   单次耗时=       0.5 ms                                                                                                                                                                                     [第 2 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 2   单次耗时=       1.1 ms                                                                                                                                                                                     [第 3 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 3   单次耗时=       0.8 ms                                                                                                                                                                                     [第 4 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 4   单次耗时=       1.5 ms                                                                                                                                                                                     [第 5 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 5   单次耗时=       2.4 ms                                                                                                                                                                                     [第 6 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 6   单次耗时=       4.6 ms                                                                                                                                                                                     [第 7 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 7   单次耗时=       8.0 ms                                                                                                                                                                                     [第 8 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 8   单次耗时=      23.5 ms                                                                                                                                                                                     [第 9 篇] 模拟一行带颜色的输出                                                                                                                                                                                                     层数= 9   单次耗时=      32.7 ms                                                                                                                                                                                     [第 10 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=10   单次耗时=      63.2 ms                                                                                                                                                                                     [第 11 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=11   单次耗时=     118.3 ms                                                                                                                                                                                     [第 12 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=12   单次耗时=     243.5 ms                                                                                                                                                                                     [第 13 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=13   单次耗时=     479.3 ms                                                                                                                                                                                     [第 14 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=14   单次耗时=     980.9 ms                                                                                                                                                                                     [第 15 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=15   单次耗时=    1896.4 ms                                                                                                                                                                                     [第 16 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    层数=16   单次耗时=    3917.2 ms                                                                                                                                                                                                                                                                                                                                                                                                            单次耗时已超过 3.0 秒 —— 演示停止。                                                                                                                                                                                             再往上加层数,终端就会明显一个字符一个字符往外蹦。                                                                                                                                                                                                                                                                                                                                                                                                         --------------------------------------------------------------                                                                                                                                                      对照:还原 sys.stdout 后只 init() 一次(修复后的写法)                                                                                                                                                                             --------------------------------------------------------------                                                                                                                                                         当前嵌套层数 = 1                                                                                                                                                                                                     [第 1 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 2 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 3 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 4 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 5 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 6 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 7 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 8 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 9 篇] 模拟一行带颜色的输出                                                                                                                                                                                                 [第 10 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 11 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 12 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 13 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 14 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 15 篇] 模拟一行带颜色的输出                                                                                                                                                                                                [第 16 篇] 模拟一行带颜色的输出                                                                                                                                                                                                    连续输出 16 篇总耗时 = 15.4 ms(平均 0.96 ms/篇)                                                                                                                                                                                                                                                                                                                                                                                              演示结束。若中间卡住过久,关闭窗口重开即可。

检测脚本:

# -*- coding: utf-8 -*-\"\"\"colorama 输出卡顿:复现与验证工具=================================背景----当脚本在处理循环里反复调用 colorama 的 init(autoreset=True) 时,sys.stdout 会被一层层包裹 AnsiToWin32,层数等于已处理条数。在 convert=False 且 strip=False 的环境下,单次 print 的底层写入次数会变成 2^层数,表现为终端一个字符一个字符往外蹦。本工具提供三种验证方式----------------------1. 环境检测  —— 看当前终端下 colorama 会自动选哪条路径2. 计数模式  —— 精确统计底层写入次数,不刷屏(默认,安全)3. 真实输出  —— 真的往终端写,肉眼感受越跑越慢(需 --real,带自动熔断)用法----python test_output_slowdown.py                  环境检测 + 计数模式(默认,安全)python test_output_slowdown.py --env            只做环境检测python test_output_slowdown.py --real           真实输出演示(会让终端变卡,可 Ctrl+C)python test_output_slowdown.py --real --max 14 --limit 5依赖:colorama\"\"\"import argparseimport sysimport time# ============================================================ 一、环境检测def probe_env():    \"\"\"检测当前终端环境下,colorama 会自动选择哪条路径\"\"\"    from colorama import ansitowin32    c = ansitowin32.AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=True)    return {        \"isatty\": sys.stdout.isatty(),        \"convert\": c.convert,        \"strip\": c.strip,        \"should_wrap\": c.should_wrap(),    }def verdict(info):    \"\"\"根据检测结果判断是否会踩指数爆炸\"\"\"    if not info[\"should_wrap\"]:        return \"不会包装 sys.stdout —— 此环境下 init() 不累积层数\"    if info[\"strip\"]:        return \"线性:strip=True 使 reset_all() 短路,递归被剪断 → 不会卡\"    if info[\"convert\"]:        return \"线性:convert=True,reset_all() 走 Win32 API 旁路 → 不会卡\"    return \"指数爆炸:convert=False 且 strip=False → reset_all() 写文本并递归 → 会卡\"def count_layers():    \"\"\"sys.stdout 当前被 colorama 包了几层(正常应恒为 1)\"\"\"    s, n = sys.stdout, 0    while True:        w = getattr(s, '_StreamWrapper__wrapped', None)        if w is None:            break        n += 1        s = w    return n# ============================================================ 二、计数模式class CountingStream(object):    \"\"\"只计数、不真写,对外伪装成 isatty=True 的真实终端\"\"\"    def __init__(self):        self.writes = 0        self.chars = 0        self.flushes = 0    def write(self, s):        self.writes += 1        self.chars += len(s)        return len(s)    def flush(self):        self.flushes += 1    def isatty(self):        return True    def fileno(self):        return -1    @property    def closed(self):        return Falsedef build_chain(base, layers, convert, strip):    \"\"\"手工套 layers 层 AnsiToWin32,绕开当前是否真 tty 的影响\"\"\"    from colorama import ansitowin32    stream = base    for _ in range(layers):        w = ansitowin32.AnsiToWin32(stream, convert=convert, strip=strip, autoreset=True)        w.call_win32 = lambda *a, **k: None   # 屏蔽真实 Win32 调用,只统计写入        stream = w.stream    return streamdef simulate(articles, convert, strip):    \"\"\"    模拟处理 articles 篇文章。    返回 ((修复前累计, 修复前峰值), (修复后累计, 修复后峰值))    修复前 = 每篇都 init() 一次;修复后 = 启动时只 init() 一次    \"\"\"    TEXT = \"\\x1b[31mID: 12345 标题: 某安全漏洞通告\\x1b[0m\"    def run(fixed):        base = CountingStream()        stream = build_chain(base, 1, convert, strip) if fixed else None        total = peak = 0        for k in range(1, articles + 1):            if not fixed:                stream = build_chain(base, k, convert, strip)   # 层数 = 第几篇            before = base.writes            stream.write(TEXT)            cost = base.writes - before            total += cost            peak = max(peak, cost)        return total, peak    return run(False), run(True)def print_table(label, convert, strip):    print(\"\
  【%s】convert=%s  strip=%s\" % (label, convert, strip))    print(\"  文章数 |  修复前(累计) | 修复前(单篇峰值) | 修复后(累计)\")    print(\"  -------+--------------+------------------+-------------\")    for n in (5, 10, 15, 18, 20):        (total_old, peak_old), (total_new, _) = simulate(n, convert, strip)        print(\"  %6d | %13d | %15d | %12d\" % (n, total_old, peak_old, total_new))# ============================================================ 三、真实输出def real_demo(max_layers, limit_sec):    \"\"\"真的往终端写,逐层累积,肉眼感受越跑越慢\"\"\"    from colorama import init, Fore    raw = sys.__stdout__          # 最原始 stdout,不受 colorama 包装影响    raw.write(\"\
\" + \"=\" * 62 + \"\
\")    raw.write(\" 真实输出演示:每\\\"篇\\\"调用一次 init(autoreset=True),再输出一行\
\")    raw.write(\" 单次耗时超过 %.1f 秒自动停止,也可随时按 Ctrl+C 中断\
\" % limit_sec)    raw.write(\"=\" * 62 + \"\
\
\")    raw.flush()    sys.stdout.write(\"下面的彩色行走的是被嵌套后的 sys.stdout:\
\")    sys.stdout.flush()    stopped = None    for k in range(1, max_layers + 1):        init(autoreset=True)                       # ← 修复前的写法:每篇一次        t = time.perf_counter()        sys.stdout.write(Fore.RED + \"[第 %d 篇] 模拟一行带颜色的输出\
\" % k)        el = time.perf_counter() - t        raw.write(\"    层数=%2d   单次耗时=%10.1f ms\
\" % (k, el * 1000))        raw.flush()        if el > limit_sec:            stopped = k            break    if stopped:        raw.write(\"\
    单次耗时已超过 %.1f 秒 —— 演示停止。\
\" % limit_sec)        raw.write(\"    再往上加层数,终端就会明显一个字符一个字符往外蹦。\
\")    else:        raw.write(\"\
    已到达设定的最大层数 %d。\
\" % max_layers)    # ---- 对照:还原后只 init 一次,即修复后的写法 ----    raw.write(\"\
\" + \"-\" * 62 + \"\
\")    raw.write(\" 对照:还原 sys.stdout 后只 init() 一次(修复后的写法)\
\")    raw.write(\"-\" * 62 + \"\
\")    sys.stdout = sys.__stdout__    init(autoreset=True)    raw.write(\"    当前嵌套层数 = %d\
\" % count_layers())    raw.flush()    n = stopped or max_layers    t = time.perf_counter()    for k in range(1, n + 1):        sys.stdout.write(Fore.GREEN + \"[第 %d 篇] 模拟一行带颜色的输出\
\" % k)    el = time.perf_counter() - t    raw.write(\"    连续输出 %d 篇总耗时 = %.1f ms(平均 %.2f ms/篇)\
\"              % (n, el * 1000, el * 1000 / n))    raw.flush()# ============================================================ 主流程def main():    ap = argparse.ArgumentParser(description=\"colorama 输出卡顿复现与验证工具\")    ap.add_argument(\"--real\", action=\"store_true\", help=\"真实输出演示(会让终端变卡)\")    ap.add_argument(\"--max\", type=int, default=12, help=\"真实模式最大层数,默认 12\")    ap.add_argument(\"--limit\", type=float, default=3.0, help=\"真实模式单次熔断秒数,默认 3.0\")    ap.add_argument(\"--env\", action=\"store_true\", help=\"只做环境检测\")    args = ap.parse_args()    print(\"=\" * 64)    print(\" colorama 输出卡顿:复现与验证工具\")    print(\"=\" * 64)    info = probe_env()    print(\"\
[1] 当前环境检测\")    for key in (\"isatty\", \"convert\", \"strip\", \"should_wrap\"):        print(\"    %-13s = %s\" % (key, info[key]))    print(\"    判定          : %s\" % verdict(info))    print(\"    当前嵌套层数  : %d\" % count_layers())    if args.env:        return    print(\"\
[2] 计数模式(不刷屏,精确统计底层写入次数)\")    print_table(\"指数路径 · 台式机 Cmder 属于此类\", convert=False, strip=False)    print_table(\"短路路径 · 非 tty / 重定向属于此类\", convert=False, strip=True)    print(\"\
  怎么读这两张表:\")    print(\"    · 指数路径:修复前单篇峰值 = 2^N,20 篇累计 209 万次写入,\")    print(\"      修复后只有 40 次 —— 差 5 万倍,这就是卡顿的来源。\")    print(\"    · 短路路径:修复前后完全持平,每篇恒定 1 次写入。\")    print(\"      层数照样在涨,却多不出任何一次写入 —— 这就是该环境下\")    print(\"      bug 不显形的直接证据。\")    if args.real:        real_demo(args.max, args.limit)        print(\"\
演示结束。若中间卡住过久,关闭窗口重开即可。\")    else:        print(\"\
[3] 真实输出演示已跳过\")        print(\"    想肉眼感受卡顿,加 --real 重跑:\")        print(\"      python %s --real\" % __file__.split(\"\\\\\")[-1].split(\"/\")[-1])        print(\"    建议先 --max 10,确认能承受再往上加。\")if __name__ == \"__main__\":    main()

命令行打印输出字符缓慢之谜" 网络安全文章

命令行打印输出字符缓慢之谜"

本文分析了Python脚本输出字符缓慢的问题,定位到colorama0.4.6版本init()方法缺乏幂等保护,在循环中多次调用导致终端控制序列处理开销增大。解决方案是仅在main中调用一次init(autoreset=True)或使用just_fix_windows\\_console(),并提供了检测代码和修复前后对比。
提示注入的形式化理论与不可能性" 网络安全文章

提示注入的形式化理论与不可能性"

本文从理论计算机科学视角形式化定义提示注入,证明注入判定不可判定及提示层防御不可能性,提出注入熵度量与计算复杂性类别,指出架构级防御可保证工具调用安全但无法完备防御语义类属性,为安全研究提供理论框架。
10月17日专题会议:人工智能安全前沿学术会议" 网络安全文章

10月17日专题会议:人工智能安全前沿学术会议"

该文档介绍2026年10月16-18日在成都举办的网络空间安全学术会议中的人工智能安全前沿专题会议。会议聚焦大模型与智能体安全、端侧智能、生成式AI安全等方向,邀请多位知名专家学者分享前沿进展,旨在推动智能系统安全可信应用。文档还包含会议主席及讲者介绍、注册费用等信息。
评论:0   参与:  0