jjzjj

记一次 Windows 下 Python 3 的控制台虚拟终端序列(控制台颜色输出, colorama 库)的踩坑经历

cup11 2023-04-15 原文

修改

2022.7.31

感谢 @DavyZhou 的评论,对文章进行了一次大修改。

2022.8.2

又踩了一次坑,再补充一下

前言

本人的电脑配置为 Windows 11 , Python 版本是 Anaconda python 3.9 ,此问题在以前用的 Windows 10, Python 3.7 应可以复现。
当然,我的解决方法未必会是最好的,如有大佬看到错误或者有更简单的方法欢迎进行指正。
(文章为了让大家了解详细过程,可能有点水,觉得废话多的可以从"如何支持虚拟终端"开始看)

参考资料

标题 网址
控制台虚拟终端序列 - Microsoft https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
console - PyPI https://pypi.org/project/console/
Python - output of [31m text instead of color - stackoverflow https://stackoverflow.com/questions/47432418/output-of-31m-text-instead-of-color
colorama - PyPI https://pypi.org/project/colorama/
Python colorama not working with input? - Stack Overflow https://stackoverflow.com/questions/32872612/python-colorama-not-working-with-input#answer-68958376

背景

最开始,我打算写一个文件数据处理小工具和别人共享,由于对性能要求的误判,我本来打算用 C++ 追求性能(做完后发现这个一周用一次的东西 C++ 最多每次能提速15秒),结果就因为同时需要输入中文文件名并且使用UTF-8编码这件事一直踩坑,(2022年了微软连utf-8都没搞明白),于是还是打算采用 Python + Pyinstaller 。

因为我不想搞太麻烦,所以就没有使用 GUI 图形界面。但是这也太单调了,我想要控制台有不同的颜色,起码好看一点。我想起了以前看到过的 colorama 库,上次在 IDLE 跑出来一堆乱码,但我想这大概是 IDLE 的问题吧。然而,事情的发展远超出我的预料,在 cmd 上居然也跑不了?不过,在 C++ 探索中,我发现了这么一个东西——

控制台虚拟终端序列

控制台虚拟终端序列是这么一个东西(以下内容是 Microsoft Windows 官方的文档):

虚拟终端序列是控制字符序列,可在写入输出流时控制游标移动、控制台颜色和其他操作。 在输入流上也可以接收序列,以响应输出流查询信息序列,或在设置适当模式时作为用户输入的编码。
可以使用 GetConsoleMode 和 SetConsoleMode 函数配置此行为 。 本文档末尾包含了建议的启用虚拟终端行为的方法的示例。
以下序列的行为基于 VT100 和派生终端仿真器技术,尤其是 xterm 终端仿真器。 有关终端序列的详细信息,请访问 http://vt100.nethttp://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 完成小项目的人不太友好。)

那么,就没有办法了吗?其实有,而且还非常简单——

方案1: colorama.init() [新方案,实用快速]

当时大意了,知道看 console 的文档忘了看 colorama 的文档了。这里顺便提一嘴, pip 安装的库都可以去 https://pypi.org 上看文档。
上文档:https://pypi.org/project/colorama/#initialisation

因此,只要加个init()初始化虚拟终端,把源代码改成这样就可以了(ps: 个人认为init 函数可以用 as 取个别名,因为太容易跟自己代码撞上了):

from colorama import init, Fore, Back, Style
init()
print(Fore.RED + "这一段字体颜色是红色" + Back.CYAN + "这一段还有青色背景" + Style.BRIGHT + "这一段更亮了" + Style.RESET_ALL + "这一段啥样式都没了")

没错,这样,就好了……吗?
其实并没有。在 Python shell 中可以实现,但是很玄学的地方就在于运行 Python 文件的时候不可以。根据 https://stackoverflow.com/questions/32872612/python-colorama-not-working-with-input#answer-68958376 ,看来如果是 Windows 平台,需要再进行一个os.system("cls")

方案2: import console [原方案,鉴于 PyInstaller 的问题不建议使用]

ps: 可能由于设备环境不同没法用(尤其是打包后)。
而且截至 2022.7.31, colorama 最近更新在 2022.7.16 ,而 console 最近更新已经在2021.4 了。

同样地,先安装库 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 + " 这一段啥样式都没了")

其实你会发现,过渡过去还挺顺滑的。

我们再使用一下原来 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这个库一旦被调用就可以直接提供虚拟终端的支持。

总结:

  1. 虚拟终端序列支持的锅
  2. 使用前先 os.system("cls")(Windows) colorama.init()

好了,这就是主要内容了,如果觉得有用或者解决了问题的话,给作者点个赞吧!

有关记一次 Windows 下 Python 3 的控制台虚拟终端序列(控制台颜色输出, colorama 库)的踩坑经历的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  5. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  6. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  7. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  10. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

随机推荐