Pythonでネストされた色付き文字を出力する


はじめ

Command Prompt, PowerShell, Terminalなどで色付き文字を出力する方法(ライブラリ)は多数あります。

有名どころで言えば

Colorama

from colorama import Fore

print(Fore.GREEN + "This text is green!")

termcolor

from termcolor import colored
print(colored('This text is green!', 'green'))

とかですかね。

素晴らしいライブラリ

これらは実に便利なライブラリです。簡単に色づき文字を作成したりすることができます。
ただ、実は一つ問題があります。

ネストできない

これはつまり、たとえば

(赤字の文章)(緑字の文章)(赤字の文章)

とか

(赤字の文章)(赤字で下線のある文章)(赤字の文章)

などを出力しようとすると、結構めんどくさいということです。
以下の画像における各行のようなものです。

たとえばこれをtermcolorで実装しようとすると、以下のようになるでしょう。

from termcolor import colored

print(colored('[RED]', 'red') + colored('[GREEN]', 'green') + colored('[RED]', 'red'))
print(colored('[RED]', 'red') + colored('[UNDERLINE]', 'red', attrs=['underline']) + colored('[RED]', 'red'))

ネストが深くなったり、長い文章同士を結合しようとすると毎回こういった指定をしなければならず、ちょっと大変です。そこで

スタイリングライブラリ iro

iroを使ってみましょう!

$ pip install iro

GitHub
さて、このライブラリを使うと、ネストされたスタイルをこのように表現することができます。
出力は上記の画像と全く同じです。

from iro import Iro, Color, Style

print(Iro([
    Color.RED, "[RED]",
    [ Color.GREEN, "[GREEN]" ],
    "[RED]"
]))

print(Iro([
    Color.RED, "[RED]",
    [ Style.UNDERLINE, "[UNDERLINE]" ],
    "[RED]"
]))

ある深さにあるstrには、その深さまでの色・スタイルがすべて適用されます。深さが深い方が優先度が高くなるので、このように簡単に表すことができます。

また、これはネストがより深くなればなるほどわかりやすくなります。

以下の二つのコードは同等の出力をします。

print(Iro([
    Color.RED, "[RED]",
    [
        Style.UNDERLINE, "[RED/UNDERLINE]",
        [
            Style.BOLD, Color.GREEN, "[GREEN/UNDERLINE/BOLD]",
            [
                Style.INVERT, "[GREEN/UNDERLINE/BOLD/INVERT]",
                [
                    Style.OFF_BOLD, "[GREEN/UNDERLINE/INVERT]"
                ]
            ],
            "[GREEN/UNDERLINE/BOLD]"
        ],
        "[RED/UNDERLINE]"
    ]
]))
print(colored("[RED]", 'red'),
      colored("[RED/UNDERLINE]", 'red', attrs=['underline']),
      colored("[GREEN/UNDERLINE/BOLD]", 'green', attrs=['underline', 'bold']),
      colored("[GREEN/UNDERLINE/BOLD/ITALIC]", 'green', attrs=['underline', 'bold', 'reverse']),
      colored("[GREEN/UNDERLINE/ITALIC]", 'green', attrs=['underline', 'reverse']),
      colored("[GREEN/UNDERLINE/BOLD]", 'green', attrs=['underline', 'bold']),
      colored("[RED/UNDERLINE]", 'red', attrs=['underline']), sep=''
      )

Iroのほうはあえて見やすさのために大量に改行などしてますが、非常に分かりやすくなっているのではないでしょうか?

これだけでなく、Iroは8-bitカラーや、RGBカラーを使用することもできます。

print(Iro([
    [ Color256(135), Color256(217, bg=True), " SEXY COLOR " ],
    [ ColorRGB.from_color_code("EEA47F"), ColorRGB(0, 83, 156, bg=True), " AND THIS IS SICK " ],
    [ Color.BRIGHT_YELLOW, Color.BG_YELLOW, " BRIGHT!! " ]
], disable_rgb=False))

デフォルトではdisable_rgbTrueで、ColorRGBをそれに最も近しいColor256に変換して出力します。

RGBカラーに対応していないコンソールも時々あるので、その時はdisable_rgbTrueのままにしておきましょう。Trueにするとこのようになります。

確かによく見てみると、上の写真と下の写真、中央の色が違いますね!(その程度の差なので、結構精度は高い)

Styleも豊富で

RESET
BOLD 
DIM 
ITALIC 
UNDERLINE 
SLOW_BLINK 
RAPID_BLINK 
INVERT 
HIDE 
STRIKE 
BLACKLETTER_FONT 
DOUBLY_UNDERLINE 

OFF_INTENSITY
OFF_BOLD
OFF_DIM
OFF_ITALIC
OFF_UNDERLINE
OFF_BLINK
OFF_INVERT
OFF_HIDE
OFF_STRIKE

OFF_COLOR
OFF_BG_COLOR

OFF_OVERLINE

などが使えます。

Iro([text...], optimize_level=1)

とすることで最適化が行われ描画が高速になる可能性がありますが、端末によっては最適化によって動作が不安定になる場合があります。ただ、この効果は大きく、GitHubのQ&Aを見ると

value = [
    Color.RED, "[RED]",
    [
        Style.UNDERLINE, "[RED/UNDERLINE]",
        [
            Style.BOLD, Color.GREEN, "[GREEN/UNDERLINE/BOLD]",
            [
                Style.INVERT, "[GREEN/UNDERLINE/BOLD/INVERT]",
                [
                    Style.OFF_BOLD, "[GREEN/UNDERLINE/INVERT]"
                ]
            ],
            "[GREEN/UNDERLINE/BOLD]"
        ],
        "[RED/UNDERLINE]"
    ]
]
print(repr(Iro(value, optimize_level=1).text))
# '\x1b[31m[RED]\x1b[4m[RED/UNDERLINE]\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]\x1b[7m[GREEN/UNDERLINE/BOLD/INVERT]
# \x1b[22m[GREEN/UNDERLINE/INVERT]\x1b[1m\x1b[27m[GREEN/UNDERLINE/BOLD]\x1b[22m\x1b[31m[RED/UNDERLINE]
# \x1b[24m\x1b[39m\x1b[0m'

print(repr(Iro(value, optimize_level=0).text))
# '\x1b[31m[RED]\x1b[31m\x1b[4m[RED/UNDERLINE]\x1b[31m\x1b[4m\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]
# \x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m[GREEN/UNDERLINE/BOLD/INVERT]
# \x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m\x1b[22m[GREEN/UNDERLINE/INVERT]
# \x1b[39m\x1b[24m\x1b[22m\x1b[39m\x1b[27m\x1b[22m\x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m\x1b[39m\x1b
# [24m\x1b[22m\x1b[39m\x1b[27m\x1b[31m\x1b[4m\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]
# \x1b[39m\x1b[24m\x1b[22m\x1b[39m\x1b[31m\x1b[4m[RED/UNDERLINE]
# \x1b[39m\x1b[24m\x1b[31m\x1b[39m\x1b[0m'

という程の変化があるようです。

おわり

是非便利にお使いください!(作者)