Python で ANSI カラーコードを持つ文字列の正しい文字列長を取得する
-
25-09-2019 - |
質問
一連のデータを適切な列形式で自動的に出力する Python コードをいくつか用意しました。これには、読みやすくするためにデータのさまざまな部分に色を付けるための適切な ASCII エスケープ シーケンスの挿入も含まれます。
最終的には、各行がリストとして表現され、各行の同じ列が常に同じ長さになるように、各項目はスペースが埋め込まれた列になります。残念ながら、これを実際に印刷しようとすると、すべての列が整列しません。これは ASCII エスケープ シーケンスに関係しているのではないかと思います。 len
関数はこれらを認識していないようです:
>>> a = '\x1b[1m0.0\x1b[0m'
>>> len(a)
11
>>> print a
0.0
したがって、各列は次のように同じ長さですが、 len
, 、画面に印刷されるとき、実際には同じ長さではありません。
エスケープされた文字列を取得して、適切にスペースを埋め込むことができるように印刷された長さを調べる方法はありますか(正規表現を使用したハッキングを行うことは避けたいですが、私はやりたくないです)。おそらく、それを文字列に「出力」して戻し、その長さを調べる何らかの方法がありますか?
解決
pyparsing wikiにはANSIエスケープシーケンスのマッチングのために、この役立つ表現するが含まれています。
ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) +
oneOf(list(alphas)))
ここでは、エスケープシーケンス-ストリッパーにこれを行う方法は次のとおりです。
from pyparsing import *
ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) +
oneOf(list(alphas)))
nonAnsiString = lambda s : Suppress(escapeSeq).transformString(s)
unColorString = nonAnsiString('\x1b[1m0.0\x1b[0m')
print unColorString, len(unColorString)
プリントます:
0.0 3
他のヒント
理解できないことが 2 つあります。
(1) それはあなたのコードであり、あなたの管理下にあります。データの長さを計算できるように、エスケープ シーケンスをデータに追加してから再度削除したいと考えていますか??パディングの計算ははるかに簡単なようです 前に エスケープシーケンスを追加します。私には何が欠けているのでしょうか?
どのエスケープ シーケンスもカーソル位置を変更しないと仮定しましょう。そうした場合、現在受け入れられている答えはいずれにせよ機能しません。
という名前のリストに各列の文字列データ (エスケープ シーケンスを追加する前) があると仮定します。 string_data
事前に決定された列幅は、という名前のリストにあります。 width
. 。次のようなことを試してください。
temp = []
for colx, text in enumerate(string_data):
npad = width[colx] - len(text) # calculate padding size
assert npad >= 0
enhanced = fancy_text(text, colx, etc, whatever) # add escape sequences
temp.append(enhanced + " " * npad)
sys.stdout.write("".join(temp))
アップデート-1
OPのコメントの後:
それらを取り除き、その後の長さを計算したい理由 文字列にカラーコードが含まれているのは、すべてのデータが構築されているためです プログラム。私はたくさんの色付けメソッドを持っていて、 データを次のようにアップします。
str = "%s/%s/%s" % (GREEN(data1), BLUE(data2), RED(data3))
色を塗るのはかなり難しいでしょう 事後のテキスト。
データがそれぞれ独自の書式設定を持つ部分で構成されている場合でも、必要に応じて表示される長さとパディングを計算できます。これを 1 つのセルの内容に対して行う関数を次に示します。
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(40, 48)
BOLD = 1
def render_and_pad(reqd_width, components, sep="/"):
temp = []
actual_width = 0
for fmt_code, text in components:
actual_width += len(text)
strg = "\x1b[%dm%s\x1b[m" % (fmt_code, text)
temp.append(strg)
if temp:
actual_width += len(temp) - 1
npad = reqd_width - actual_width
assert npad >= 0
return sep.join(temp) + " " * npad
print repr(
render_and_pad(20, zip([BOLD, GREEN, YELLOW], ["foo", "bar", "zot"]))
)
句読点によって通話に過度の負担がかかっていると思われる場合は、次のようなことができます。
BOLD = lambda s: (1, s)
BLACK = lambda s: (40, s)
# etc
def render_and_pad(reqd_width, sep, *components):
# etc
x = render_and_pad(20, '/', BOLD(data1), GREEN(data2), YELLOW(data3))
(2) なぜ Python に付属の正規表現キットを使用しないのかわかりません。「ハッカリー」は含まれていません(私が知っている「ハッカリー」の意味については)。
>>> import re
>>> test = "1\x1b[a2\x1b[42b3\x1b[98;99c4\x1b[77;66;55d5"
>>> expected = "12345"
>>> # regex = re.compile(r"\x1b\[[;\d]*[A-Za-z]")
... regex = re.compile(r"""
... \x1b # literal ESC
... \[ # literal [
... [;\d]* # zero or more digits or semicolons
... [A-Za-z] # a letter
... """, re.VERBOSE)
>>> print regex.findall(test)
['\x1b[a', '\x1b[42b', '\x1b[98;99c', '\x1b[77;66;55d']
>>> actual = regex.sub("", test)
>>> print repr(actual)
'12345'
>>> assert actual == expected
>>>
アップデート-2
OPのコメントの後:
ポールの答えの方が簡潔なので、私はまだポールの答えを好みます
これ以上に簡潔なことは何でしょうか?次の正規表現の解決策は十分簡潔ではないでしょうか?
# === setup ===
import re
strip_ANSI_escape_sequences_sub = re.compile(r"""
\x1b # literal ESC
\[ # literal [
[;\d]* # zero or more digits or semicolons
[A-Za-z] # a letter
""", re.VERBOSE).sub
def strip_ANSI_escape_sequences(s):
return strip_ANSI_escape_sequences_sub("", s)
# === usage ===
raw_data = strip_ANSI_escape_sequences(formatted_data)
[上記のコードは、@Nick Perkins が機能しないと指摘したため修正されました]
ANSI_escape_code の中で見ると、あなたの例の配列があります を選択し、グラフィックレンディション(おそらく太字)の。
はのカーソル位置する(CSI n ; m H
)配列を有するコントロールカラム位置決めしようとします。
この方法では、前のテキストの幅は、現在の列の位置には影響しないと、文字列の幅を心配する必要はありません。
より良いオプション、UNIXをターゲット場合は、呪いモジュールウィンドウを使用しています-objectsする。 例えば、ストリングが有する画面上に配置することができます。
window.addnstr([y, x], str, n[, attr])
ディスプレイ上に以前に何を上書き、ATTR属性を(X、Y)で文字列strの最もn文字にペイントします。
あなただけのいくつかのセルに色を追加している場合、あなたは(それをオフにする色、4をオンにする5隠された文字)予想セル幅に9を追加することができ、例えば
import colorama # handle ANSI codes on Windows
colorama.init()
RED = '\033[91m' # 5 chars
GREEN = '\033[92m' # 5 chars
RESET = '\033[0m' # 4 chars
def red(s):
"color a string red"
return RED + s + RESET
def green(s):
"color a string green"
return GREEN + s + RESET
def redgreen(v, fmt, sign=1):
"color a value v red or green, depending on sign of value"
s = fmt.format(v)
return red(s) if (v*sign)<0 else green(s)
header_format = "{:9} {:5} {:>8} {:10} {:10} {:9} {:>8}"
row_format = "{:9} {:5} {:8.2f} {:>19} {:>19} {:>18} {:>17}"
print(header_format.format("Type","Trial","Epsilon","Avg Reward","Violations", "Accidents","Status"))
# some dummy data
testing = True
ntrials = 3
nsteps = 1
reward = 0.95
actions = [0,1,0,0,1]
d = {'success': True}
epsilon = 0.1
for trial in range(ntrials):
trial_type = "Testing " if testing else "Training"
avg_reward = redgreen(float(reward)/nsteps, "{:.2f}")
violations = redgreen(actions[1] + actions[2], "{:d}", -1)
accidents = redgreen(actions[3] + actions[4], "{:d}", -1)
status = green("On time") if d['success'] else red("Late")
print(row_format.format(trial_type, trial, epsilon, avg_reward, violations, accidents, status))
タグの付与