エスケープシーケンスの使用
エスケープシーケンスは、ESC+[で始めます(制御シーケンス開始とも言います)。ESC文字は出力不能で、chr(27)または\x1b(16進表記)でも指定できます。033は27の8進表記です。\033[の次の31mは、テキストの色を赤に変更するコードです。次に対象テキスト(Red Text)が続き、最後にテキストの色をデフォルトに戻すコード(\033[0m)を指定します。Pythonの方では8進表記が0(number}から0O{number}に変更になりましたが、ANSIエスケープシーケンスは今も0{number}表記を使用する端末に対応しています。
エスケープシーケンスはさまざまな操作に使用でき、文字色や背景色の変更、画面上のカーソルの移動(特定の場所への出力)、画面の特定部分の消去、カーソルの表示/非表示、スクリーンバッファのスクロールなどができます。ここでは色の変更の例のみ説明します。
ある小さなモジュールに、文字列、文字色、背景色の3つの引数を受け取り、該当するANSIエスケープシーケンスで文字列を囲むcolorize()という関数を作成するとします。まず、文字列からANSIエスケープコードにマップされたすべての文字色と背景色を含む、小さなグローバルディクショナリを用意します。関数自体では、文字色と背景色またはその一方がredやgreenのような名前で指定されているかどうかを検査し、ディクショナリから対応するコードを見つけ、指定の色に変更する適切なエスケープシーケンスを用意します。最後にすべてを通常の設定に戻します。ここに示すコードにはエラー処理がないので、指定の色がない場合にはKeyError例外が発生します。
colors = ['black', 'red', 'green', 'orange', 'blue', 'magenta', 'cyan', 'white']
color_dict = {}
for i, c in enumerate(colors):
color_dict[c] = (i + 30, i + 40)
def colorize(text, color=None, bgcolor=None):
c = None
bg = None
if color is not None:
c = color_dict[color][0]
if bgcolor is not None:
bg = color_dict[bgcolor][1]
s = ''
if c is not None:
s = '\033[%dm' % c
if bg is not None:
s += '\033[%dm' % bg
return '%s%s\033[0m' % (s, text)
このコードを使って、さまざまな文字色と背景色の出力を試すことができます。以下は、白のテキストとマゼンタの背景を出力する例です。
print(colorize('White on Magenta', 'white', 'magenta'))
このコードとcolorizeモジュールは、Python 2.xと3.0のどちらでも動作します。
colorize()関数を習得すれば、それ自体の色を書式指定するColorStringクラスも作成できます。その基本的な方法は、組み込みのstrクラスからサブクラスを作成し、__format__()メソッドを追加し、このメソッドが受け取るformat_specを文字色としてcolorize()関数に渡し、その関数から囲まれた文字列を返す、というものです。
class ColorString(str):
def __format__(self, format_spec):
s = colorize(self, format_spec)
return s
この実装では、背景色は変更できず、文字色の変更だけですが、書式指定が非常に簡潔になります(色名を指定するだけです)。以下は、ColorStringの使用例です。この例では、まず、1つの文「'Yeah, it works!'」を分割してColorStringの単語のリストを用意し、次にred、green、blueという色の書式指定文字列を指定して、各単語をそれぞれ異なる色で出力します。
words = [ColorString(x) for x in 'Yeah, it works!'.split()]
print('{0:red} {1:green} {2:blue}'.format(*words))
Python 3.0には、単一のオブジェクトの書式指定に使用するformat()グローバル関数もあります。これは単純にオブジェクトの__format__()メソッドを呼び出します。ColorStringの例では次のようになります。
>>> format(ColorString('Gigi'), 'red')
'\x1b[31mGigi\x1b[0m'
このサブクラス化スキームは有効に機能しますが、カスタム書式指定を行うたびに、__format__()メソッドを持つ特別なクラスを作成するのは、少し面倒な気もします。また、このようなサブクラス化では、書式指定を利用する開発者がColorStringのような特別なオブジェクトを作成する必要が生じます。幸い、独自の書式指定クラスを実装してあらゆる型の書式指定に使用することもできます。例えば、単にテキストを指定の色で出力できれば便利です。以下はColorFormatterというクラスの例です。このクラスはstring.Formatterクラスのサブクラスで、format_fieldメソッドをオーバーライドします。このオーバーライドでは、colorsリスト内にformat_specがあればフィールドの色を変更し、なければFormatter.format_field()を呼び出してデフォルトの書式指定を適用します。
from string import Formatter
class ColorFormatter(Formatter):
def format_field(self, value, format_spec):
if format_spec in colors:
return colorize(value, format_spec)
else:
return Formatter.format_field(self, value, format_spec)
カスタム書式指定クラスを使用するには、それをインスタンス化し、format()メソッドを呼び出して書式指定された文字列を取得する必要があります。これをもっと簡潔にするために、バインドされたformat()メソッドをfという変数に割り当てると、さらに使いやすくなります。
formatter = ColorFormatter()
f = formatter.format
print(f('{0:cyan} works very {1:orange}.', 'ColorFormatter', 'well'))
フィールド値のリストまたは名前付きフィールドのディクショナリがある場合には、vformat()メソッドを使用できます。このメソッドは、位置指定引数としてリストを、キーワード引数としてディクショナリを受け取ります。
formatter = ColorFormatter()
f = formatter.vformat
args = ['The', 'vformat()']
kwargs = dict(m='method', t='too')
print(f('{0:red} {1:blue} {m:green} works {t:magenta}', args, kwargs)
まとめ
Windows開発者がPython 3.0を少し勉強したいという場合は、色のANSIエスケープコードをWindowsに実装する、新しい代替print()関数を作成してみるとよいでしょう。その代替print()関数では、出力するテキストをスキャンしてANSIエスケープシーケンスを探し、それを解析し、SetConsoleTextAttribute() APIを使用して該当する色設定を適用します。
この記事では、さまざまな例を示して、Python 3.0の細かい変更点がデータ型、算術演算、文字列書式指定に及ぼす影響について説明しました。これ以外にも、Python 3.0では標準ライブラリに重要な変更が行われており、それについてはこのシリーズの次回の記事で説明します。
