程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

記一次 Windows 下 Python 3 的控制台虛擬終端序列(控制台顏色輸出, colorama / console 庫)的踩坑經歷

編輯:Python

前言

本人的電腦配置為 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 很多東西挺玄學的。有遇到相同問題的可以借鑒一下。


總結:

  1. 虛擬終端序列支持的鍋
  2. 盡量不要用colorama,改用console

好了,這就是主要內容了,如果覺得有用或者解決了問題的話,給作者點個贊吧!


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved