前言
本人的电脑配置为 Windows 11 , Python 版本是 Anaconda python 3.9 ,此问题在以前用的 Windows 10, Python 3.7 应可以复现。
在此感谢由 https://www.cnblogs.com/pylemon/archive/2011/06/11/2078456.html 带来的灵感,希望能把经历分享给大家。
当然,我的解决方法未必会是最好的,如有大佬看到错误或者有更简单的方法欢迎进行指正。
(文章可能有点水,觉得废话多的可以从"如何支持虚拟终端"开始看)
背景
最开始,我打算写一个文件数据处理小工具和别人共享,由于对性能要求的误判,我本来打算用 C++ 追求性能(做完后发现这个一周用一次的东西 C++ 最多每次能提速15秒),结果就因为同时需要输入中文文件名并且使用UTF-8
编码这件事一直踩坑,(2022年了微软连utf-8都没搞明白),于是还是打算采用 Python + Pyinstaller 。
因为我不想搞太麻烦,所以就没有使用 GUI 图形界面。但是这也太单调了,我想要控制台有不同的颜色,起码好看一点。我想起了以前看到过的 colorama 库,上次在 IDLE 跑出来一堆乱码,但我想这大概是 IDLE 的问题吧。然而,事情的发展远超出我的预料,我在“控制台虚拟终端序列”上
控制台虚拟终端序列
控制台虚拟终端序列是这么一个东西(以下内容是 Microsoft Windows 官方的文档):
虚拟终端序列是控制字符序列,可在写入输出流时控制游标移动、控制台颜色和其他操作。 在输入流上也可以接收序列,以响应输出流查询信息序列,或在设置适当模式时作为用户输入的编码。
可以使用 GetConsoleMode 和 SetConsoleMode 函数配置此行为 。 本文档末尾包含了建议的启用虚拟终端行为的方法的示例。
以下序列的行为基于 VT100 和派生终端仿真器技术,尤其是 xterm 终端仿真器。 有关终端序列的详细信息,请访问 http://vt100.net 和 http://invisible-island.net/xterm/ctlseqs/ctlseqs.html。
相信大家也发现了微软写的文档(尤其是中文版)属实写的有点不明不白的,而且整个页面给一堆链接也不知道该往哪去找。
简而言之,虚拟终端序列是一串控制字符序列,它们都以ESC
开始( ASCII 编码为十进制 27 ,八进制 33 ,十六进制 1B ,因此在 Python 中可以写作\033
或\x1b
),而后大多数会跟一个[
或(
,再加上核心数字,最终一个字母。
在此,我们不讨论那么多,我当时只想要颜色,那么根据文档,很显然我们应该找“文本格式”,不过微软没有其他博主总结的好,搜一下Python 控制台彩色输出
结果一大把。
但是我们不需要了解那么多,我们只需要一个库——
colorama
先进行一下pip install colorama
。
如果你有IPython
,你可以试一下这串代码:
from colorama import Fore, Back, Style
print(Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景"
+ Style.BRIGHT + "这一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了")
结果大概是这样(挺不错的):
不过,Colorama并不是本文的重点,网上的教程也是一搜一大把,但是问题就在于:
Colorama 靠什么实现?
我们注意到Colorama
的样式可以直接与字符串进行+
运算,这并不是因为它进行了重载,而是因为它就是一串字符串。不信看看:
In [1]: from colorama import Fore, Back, Style
In [2]: Fore.RED
Out[2]: '\x1b[31m'
In [3]: type(Fore.RED)
Out[3]: str
In [4]: Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景"
...: + Style.BRIGHT + "这一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了"
Out[4]: '\x1b[31m这一段字体颜色是红色\x1b[46m这一段还有青色背景\x1b[1m这一段更亮了\x1b[0m这一段啥样式都没了'
对 C++ 控制台有过一定深入操作经验的肯定知道, C++ 很长一段时间都是调用 windows.h
内的 API 来实现的,但是现在这些繁杂的语法已经被 Microsoft 建议弃用了,同样改用虚拟终端序列。(它不仅可以在一定程度上降低或升高代码的复杂度,还可以更好地进行跨平台的支持(注:这一点目前还不清楚,不过趋势是这样)。)
那么,靠虚拟终端序列有什么问题吗?当然有。
天坑 - 虚拟终端的支持
上面我在做Colorama
的示例的时候使用了IPython
而不是Python
自带的 shell ,这不止是因为IPython
使代码更好看、操作更方便,更主要是因为:
用 Python 自带的 Shell 它根本就不行!!!
不信我们试试:
打开默认的 cmd 或 Powershell ,输入"Python",再把刚才的代码输入一遍:
>>> from colorama import Fore, Back, Style
>>> print(Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景" + Style.BRIGHT + "这一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了")
然后你就会惊喜地发现输出是这样的:
看到问题了吗?ESC字符输出成了乱码, cmd 下的 Python 压根儿就不支持虚拟终端!
Win11 有个“终端”软件,用它试试:
(这咋又好了???)
再用 VS Code 试试。
不发图了,也可以支持。
然而,如果我们直接使用命令行运行 .py 文件或是直接双击打开,我们会发现它也是不支持颜色的。
上面说过了,“终端”软件是支持的,而 cmd 和 Powershell 是不支持的。
PyInstaller 由于变相打包了个 Python 虚拟机进去,所以生成的 .exe 文件的支持情况与上述打开方式完全相同。
什么原因?
其实原因本质很简单:是否已经支持虚拟终端序列。
Windows 自带的 cmd 和 powershell 是不支持虚拟终端序列的。 Python 本身也不会自动启用。因此,两者一起,就无法正常输出了。
但是,“终端”软件默认开启了虚拟终端序列的支持,而 IPython 本身就一直在不停在使用控制台高级技能,所以某种程度上来说也是内置了。
如何支持虚拟终端?
其实这回 Microsoft 文档做的还算不错,给了一个示例:
https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
如果你懒得翻问题也不大,但是有一个小小的问题:它好像是 C 语言诶(
好,那么现在你可以写一个 C-Extension 或者用 Cython 把它拿进来。
说实话这个方法应该也行,不过我们看看有没有更好的写法。(上述代码明显只能针对 Windows ,而且对嫌麻烦、开发经验不多或者希望只用 Python 完成小项目的人不太友好。)
那么,就没有办法了吗?其实有,而且还非常简单——
import console
同样地,先安装库 pip install console
官方文档在这:https://pypi.org/project/console/
(顺便说一句:安装了 console 就基本不需要 colorama 了, console 的支持非常全面,当然,模块加载时间也比较长。前面介绍 colorama 只不过是因为它宣传的最多,而且很少有人指出它的致命缺点。)
再来试试,这次换个写法:
from console import fg, bg, fx
print(fg.red + "这一段字体颜色是红色" + bg.cyan + "这一段还有青色背景" + fx.blink + "这一段更亮了" + fx.end + " 这一段啥样式都没了")
其实你会发现,过渡过去还挺顺滑的。而且console
提供了更全面的支持,建议试一下。
玄学时刻1 - 调用即生效
我们再使用一下原来 cmd + Python + colorama 的配置,只不过加一个 import console
,输入:
import console
from colorama import Fore, Back, Style
print(Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景" + Style.BRIGHT + "这 一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了")
然后你就会发现:“console 我连用都没用,咋就又支持了?”运行效果(由于图一样,我就偷个懒,把之前的复制一份):
为什么会这样?(这里做一下浅层次的解析):
找一下console
的源码:
在__init__.py
里有:
# detection is performed if not explicitly disabled
if (_env.PY_CONSOLE_AUTODETECT.value is None or
_env.PY_CONSOLE_AUTODETECT.truthy):
# detect palette, other modules are dependent
from .detection import init as _init
term_level = _init(using_terminfo=using_terminfo)
而且 if 的表达式在普通环境下一般是True
,所以就会调用.detection.init
。
再继续看看detection.py
:
... 我好像高估我自己了,有兴趣的自己可以去研究一下,大概是env
这个库的原因。
总之,这一机制使得console
这个库一旦被调用就可以直接提供虚拟终端的支持。
你可以将colorama
转换为console
,也可以不转。
最后一个问题:那么,如果这个软件需要PyInstaller
打包呢?
【已无法复现】玄学时刻2: colorama + console + PyInstaller 库缺失
如果你用 console 用到底,那最后是不会有什么大事的(所以我建议还是用console
):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from console import fg, bg, fx # type: ignore
print(fg.red + "这一段字体颜色是红色" + bg.cyan + "这一段还有青色背景" + fx.blink
+ "这一段更亮了" + fx.end + "这一段啥样式都没了")
input("按回车键退出:")
打包并运行,发现结果正常。
但是:如果你用 colorama ,并且真的只是调用了一下 import console
,那么:
由于(大概) PyInstaller 打包时为了防止文件过大,会把用不到的库或函数扔掉,所以就会有问题。
把代码改一下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from colorama import Fore, Back, Style # type: ignore
import console # type: ignore
print(Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景"
+ Style.BRIGHT + "这一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了")
input("按回车键退出:")
发现运行正常,打包一下:
翻车了,这次还是正常的,不过之前那一次提示说缺失了 console.detection
,我又加一行import console.detection
就好了,总之 Python 很多东西挺玄学的。有遇到相同问题的可以借鉴一下。
总结:
- 虚拟终端序列支持的锅
- 尽量不要用
colorama
,改用console
好了,这就是主要内容了,如果觉得有用或者解决了问题的话,给作者点个赞吧!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。