Получение правильной длины строки в Python для строк с цветовыми кодами ANSI

StackOverflow https://stackoverflow.com/questions/2186919

  •  25-09-2019
  •  | 
  •  

Вопрос

У меня есть какой-нибудь код Python, который автоматически распечатает набор данных в приятном формате столбца, в том числе введении соответствующих последовательностей Escape ASCII для цвета различных деталей данных для чтения.

В конечном итоге я в конечном итоге в конечном итоге с каждой строкой представлен в виде списка, при этом каждый элемент является столбцом, который является пробельным, так что одни и те же столбцы на каждой строке всегда одинаковы длина. К сожалению, когда я действительно собираюсь распечатать это, не все колонны поднимаются вверх. Я подозреваю, что это связано с ASCII escape Sequences - потому что len Функция, кажется, не распознает эти:

>>> a = '\x1b[1m0.0\x1b[0m'
>>> len(a)
11
>>> print a
0.0

И поэтому, пока каждый столбец одинаковой длины в соответствии с len, они на самом деле не одинаковы длина, когда напечатаны на экране.

Есть ли какие-либо способы (сохранить для совершения какого-то булавки с регулярными выражениями, которые я бы предпочел не делать), чтобы взять сбежавную строку и узнать, что напечатанная длина, чтобы я могу космическую прокладку? Может быть, какой-то способ просто «печатать» его обратно в строку и изучить длину этого?

Это было полезно?

Решение

Pyparding Wiki включает в себя это полезное выражение Для сопоставления на Ansi Escape Sequences:

ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) + 
                oneOf(list(alphas)))

Вот как сделать это в escape-Sequence-Stripmer:

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

Другие советы

Я не понимаю двух вещей.

(1) Это ваш код под вашим контролем. Вы хотите добавить дополнительные последовательности к вашим данным, а затем снова распределить их, чтобы вы могли рассчитать длину ваших данных ?? Кажется намного проще рассчитать прокладку до Добавление escape последовательностей. Что мне не хватает?

Давайте предположим, что ни один из последовательностей побега не изменит положение курсора. Если они это сделают, в настоящее время принятый ответ не будет работать все равно.

Предположим, что у вас есть строковые данные для каждого столбца (перед добавлением escape последовательности) в списке с именем 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.

После комментария ОП:

Причина, по которой я хочу распределить их, и рассчитать длину после того, как строка содержит цветные коды, потому что все данные создаются программно. У меня есть куча методов окрашивания, и я наращиваю данные что-то вроде этого: str = "%s/%s/%s" % (GREEN(data1), BLUE(data2), RED(data3)) Было бы довольно сложно раскрасить текст после факта.

Если данные создаются из кусочков каждый со своим собственным форматированием, вы все равно можете вычислить отображаемую длину и подушку в зависимости от обстоятельств. Вот функция, которая делает это для одного содержимого клетки:

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) Я не понимаю, почему вы не хотите использовать прилагаемый набор регулярного выражения Pyston 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.

После комментария ОП:

Я все еще предпочитаю ответ Павла, так как оно более лаконично

Более краткий, чем что? Разве это не следующее решение Regex, которое достаточно красно для вас?

# === 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, использует Курсы модуля окна-объектыОтказ Например, строка может быть расположена на экране с:

window.addnstr([y, x], str, n[, attr])

Краска на большинстве n символов строки STR AT (Y, X) с атрибутами ATTR, перезаписывая все ранее на дисплее.

Если вы просто добавляете цвет в некоторые ячейки, вы можете добавить 9 к ожидаемой ширине клеток (5 скрытых символов, чтобы включить цвет, 4, чтобы выключить его), например,

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))

Давать

screenshot

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top