はじめに
crayons はPythonのターミナルに色をついたテキストを表示するためのモジュールです。どのようにしてターミナルのテキストの色を変更しているのかが気になったので、crayonsのコードを読みつつ調べたときのメモです。
使い方
crayons を読み解く前に、簡単に使い方について触れておきます。色のついたテキストを表示するには、その色に対応した関数を呼び出すだけです。bold=True
とすることで太字にすることもできます。
import crayons
print(crayons.red("red text"))
print(crayons.blue("blue text", bold=True))
実行すると、上図のように色のついたテキストを表示できます。他にも、赤、緑、黄、青、黒、橙、青緑、白を指定できます。crayons.disable()
とすることで各種関数で色がつかないようにすることもできます。以上のように、非常にシンプルに使えるライブラリとなっています。
該当箇所のコードを読む
crayons.py の red
や blue
といった関数を見ると、ColoredString
クラスのインスタンスを作成していることがわかります。
def red(string, always=False, bold=False):
return ColoredString('RED', string, always_color=always, bold=bold)
作成されたColoredStringインスタンスを print
で表示すると、色のついたテキストが表示されます。print
時にColoredStringクラスに定義されている __unicode__
が呼び出されるためです。この関数の中では、color_str
関数が呼び出されています。
def __unicode__(self):
value = self.color_str
if isinstance(value, bytes):
return value.decode('utf8')
return value
__unicode__
から呼ばれている color_str
関数にテキストの色を変更するコードが含まれています。以下は、 color_str
の色を変更している部分を抜粋したものです。%
演算子は古くからあるPythonの文字列フォーマットです。
c = '%s%s%s%s%s' % (getattr(colorama.Fore, self.color),
getattr(colorama.Style, style),
self.s,
colorama.Fore.RESET,
getattr(colorama.Style, 'NORMAL'))
self.s
が変換対象の文字列で、その前後が colorama
というモジュールの値で挟まれていることがわかります。ターミナルに色付きの文字を表示する鍵は colorama にありそうです。
colorama
coloramaは、ANSI escape codeのショートハンドを提供するライブラリです。crayonsは、colorama経由で ANSI escape code を使い、色付きのテキストを表示しています。
ANSI escape code とは、ターミナル操作のための特殊文字列のようなものです(参考)。テキストに色をつけるだけではなく、カーソルの位置を移動したり、スクロールしたり、テキストの背景色を変更したりもできます。
coloramaのコードを読む
colorama/ansi.py に ANSI escape codeに該当する部分がありました。ここを読んでいきます。
エスケープコード
エスケープコードは、ESC[
ではじまるフォーマットで書かれます。ただし、ESC
はエスケープを表すものであり、実際にこの文字列を書くわけではありません。エスケープに対応するASCIIコードを書きます。colorama では、ソースコードの冒頭でエスケープのための文字列が定義されています。
CSI = '\033['
エスケープはASCIIコードの「27」に該当します。これを8進数で表すと 33
となります。ASCIIコードは10進数で指定できないため、このように8進数で定義しています。16進数で指定することもでき、その場合は x1b
となります。8進数でも16進数でも結果は同じです。Pythonはエスケープシーケンスを\
で表現するので、組み合わせて \033
と表現しています。もちろん、\x1b
でも構いません。
テキストの色やスタイルを設定するには、それらに対応したコード<code>
を \033
と m
で挟んだ以下のフォーマットで書くようです。<code>
には数値が入ります(参考)。
'\033<code>m`
カラーコードの定義
colorama/ansi.py の中には、AnsiCursor
AnsiFore
AnsiBack
AnsiStyle
といったクラスが定義されており、それぞれのクラスにはターミナルの操作に応じたコードが指定されています。テキストの色に関するコードは、AnsiForeクラスに定義されています。
class AnsiFore(AnsiCodes):
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
RESET = 39
AnsiCodesというクラスを継承しているようなので、こちらを見てみましょう。見やすさのためにコメントを省略しています。
class AnsiCodes(object):
def __init__(self):
for name in dir(self):
if not name.startswith('_'):
value = getattr(self, name)
setattr(self, name, code_to_chars(value))
def code_to_chars(code):
return CSI + str(code) + 'm'
dir
でクラスに関する情報をすべて取得しています。_
ではじまる名前のものは処理しないようにしています。これは、__xxx__
のような関数はPythonの特別な関数であるため処理しないようにしています。AnsiCodesを継承するクラスのインスタンスが作成されると、__init__
が実行されて、BLACKやREDなどの属性にcode_to_chars
をかませています。
code_to_chars
は色を表す値に対して、先頭にエスケープコード、末尾に m をつけています。code_to_chars
によって作られた文字列は、setattr
でインスタンス変数と設定されます。同じ名前のクラス変数とインスタンス変数がある場合は、インスタンス変数が優先されます。実際に値を取得してみると、以下のようになります。
>>> form colorama import Fore
>>> Fore.BLACK
'\x1b[30m'
>>> Fore.RED
'\x1b[31m'
>>> Fore.GREEN
'\x1b[32m'
>>> Fore.RESET
'\x1b[39m'
Pythonのインタプリタでは、\033
が \x1b
へと自動的に変換されて表示されるようです。このコードを print
等で実行すれば、以降のテキストの文字色が変わることが確認できると思います。
リセットコードについて
変更内容のリセットは明示的にする必要があり、リセットに対応したエスケープコードをつけます。Fore.RESET
の値がリセットに対応するコードです。ターミナルを操作する場合は、リセットも意識する必要があります。たとえば、一度青色にして、元の色に戻したい場合は以下のようにします。
'\033[34mblue color text\033[39m default color text' # printなどで出力する
crayons の color_str
では、対象文字列の末尾にリセットコードをつけているので、自分でリセットする必要はありません。
複数のエスケープシーケンスを指定する
先ほど見た crayons のコードをもう一度確認すると、「テキストの色」と「テキストのスタイル」のために複数のコードを連続で指定しているのがわかります。
c = '%s%s%s%s%s' % (getattr(colorama.Fore, self.color),
getattr(colorama.Style, style),
self.s,
colorama.Fore.RESET,
getattr(colorama.Style, 'NORMAL'))
正確には、「文字色のコード + スタイルのコード + テキスト + スタイルのリセットコード + 文字色のリセットコード」で構成されています。たとえば、赤色+太字を指定した場合、cの値は以下のようになります。1
が太字、31
が赤色のためのコードです。0
と 39
はそれぞれスタイルと文字色のリセットコードです。
'\033[1m\033[31mred bold text\033[39m\033[0m'
別々に指定するのではなく、;
区切りで一度に指定することもできます。
'\033[1;31mred bold text\033[0;39m'
背景色の指定
ついでなので背景色も指定してみます。ボールドにする値「1」、背景色を赤色にするための値「41」、テキストをシアンにする値「36」を指定してみます。
'\033[1;41;36mred bold text\033[0;49;39m'
なお、crayons には背景色を指定する方法がありません。もし背景色を変えたいなら、自分で対処する必要があります。
さいごに
以上、ターミナルのテキストの色を変える方法を crayons と colorama から理解したときのメモでした。ANSI escape codeを指定することでターミナルに対する操作が行え、その操作の中に色を変更する操作があるということがわかりました。基本的にライブラリを使うので、直接これらのコードを使う機会はあまりありませんが、知っておいて損はないかと思います。
自分の知らない仕様をコードから理解するのも面白いなぁと思い、OSSのコードと絡めた記事にしてみました。