Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

В википедии pyparsing это полезное выражение для сопоставления в escape-последовательностях ANSI:

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

Здесь, как сделать это в escape-sequence-stripper:

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) Это ваш код под вашим контролем. Вы хотите добавить escape-последовательности в свои данные, а затем вывести их снова, чтобы вы могли рассчитать длину ваших данных? Кажется гораздо проще рассчитать добавку до, добавляя 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))

Обновить после комментария OP "" Причина, по которой я хочу вычеркнуть их и рассчитать длину после того, как строка содержит цветовые коды, состоит в том, что все данные создаются программно. У меня есть группа методов раскраски, и я собираю данные примерно так: 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) Я не понимаю, почему вы не хотите использовать набор регулярных выражений, поставляемый с 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
>>>

Обновить после комментария 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, указал, что он не работает]

Ответ 3

В ANSI_escape_code последовательность в вашем примере Выберите "Графическое представление" (возможно, жирный).

Попробуйте управлять позиционированием столбцов с помощью последовательности CUrsor Position (CSI n ; m H). Таким образом, ширина предыдущего текста не влияет на текущую позицию столбца, и нет необходимости беспокоиться о ширине строки.

Лучший вариант, если вы нацеливаете Unix, использует оконные объекты модуля curses. Например, строка может быть размещена на экране с помощью:

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

Нарисуйте не более n символов строки str at (y, x) с атрибутами attr, перезаписывая что-либо ранее на дисплее.

Ответ 4

Если вы просто добавляете цвет в некоторые ячейки, проще просто добавить 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):
    return RED + s + RESET
def green(s):
    return GREEN + s + RESET
def redgreen(v, fmt, sign=1):
    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")
# loop
    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)