PyInstaller编译Python程序中print()抛出OSError: [Errno 22]异常排查
从你的问题描述和错误日志来看,这个间歇性出现的错误完全是Windows控制台输出句柄的不稳定导致的,结合你使用的PyInstaller 3.5版本,具体成因可以拆解为以下几点:
核心原因
PyInstaller 3.5的已知控制台I/O bug
PyInstaller 3.5是2019年的旧版本,在Windows下打包控制台程序时,对标准输出(stdout)的句柄管理存在缺陷。当你的程序持续运行(每秒输出3次日志),系统资源调度、控制台窗口状态变化(比如最小化、快速切换窗口)或者后台运行时的会话波动,都可能导致控制台输出句柄临时失效。此时print()调用底层Windows API时会传入无效参数,直接抛出Errno 22错误。控制台缓冲区的异常状态
长时间持续输出日志会让控制台缓冲区处于高负载状态,偶尔会出现系统未及时清理缓冲区、缓冲区溢出的情况,触发输出调用失败。而日志写入文件不受影响,是因为文件I/O和控制台I/O用的是完全独立的句柄和处理逻辑——文件路径是稳定的,不会受控制台状态干扰。后台运行的控制台会话问题
如果你的exe是通过脚本、任务计划或其他后台方式启动(而非直接双击打开控制台窗口),Windows可能会在某些场景下临时断开控制台会话的连接,导致print()找不到有效的输出目标,从而抛出“无效参数”的错误。
快速验证方法
你可以通过以下操作确认这个推测:
- 把PyInstaller升级到4.0以上的稳定版本,重新打包程序——3.5的很多控制台I/O问题在后续版本中已经被修复。
- 尝试用
program.exe > output.log命令运行程序,把标准输出重定向到文件,如果不再出现错误,就坐实了是控制台输出的问题。 - 直接用Python解释器运行
program.py,观察几小时,如果不会报错,进一步确认是PyInstaller打包后的专属问题。
可行的解决方案
升级PyInstaller(最直接有效)
执行pip install --upgrade pyinstaller,把版本更新到最新稳定版,重新打包。后续版本针对Windows控制台输出做了大量优化,包括修复句柄泄漏、无效句柄的问题,大概率能直接解决你的异常。在打包环境下禁用控制台print(推荐)
既然你已经有成熟的日志写入逻辑,完全可以在打包后的exe中跳过控制台输出,只保留文件日志。利用PyInstaller打包后会添加的sys._MEIPASS属性来判断运行环境:
import sys from traceback import format_exc def log_text(self, text, log_to_disk=True): log_text = time_now() + str(text) # 仅在本地开发环境(非打包exe)下打印到控制台 if not hasattr(sys, '_MEIPASS'): try: print(log_text) except: self.log_to_txt(format_exc()) self.log_to_txt(f"Print statement raised error while printing '{log_text}'") if log_to_disk: self.log_to_txt(log_text)
- 重定向stdout到日志文件
在程序启动时把标准输出和错误输出都重定向到日志文件,这样所有print()都会自动写入文件,彻底绕开控制台I/O的问题:
import sys import os # 仅在打包后的exe环境中执行重定向 if hasattr(sys, '_MEIPASS'): log_path = os.path.join(os.path.dirname(sys.executable), 'console_output.log') # 打开日志文件,追加模式 console_log = open(log_path, 'a', encoding='utf-8') # 重定向标准输出和错误输出 sys.stdout = console_log sys.stderr = console_log
内容的提问来源于stack exchange,提问作者Darius




