Ottenere corretta lunghezza della stringa in Python per le stringhe con codici colore ANSI

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

  •  25-09-2019
  •  | 
  •  

Domanda

Ho un po 'di codice Python che verrà stampata automaticamente una serie di dati in un formato colonna di bello, compresa l'istituzione di appropriate sequenze di escape ASCII al colore vari pezzi di dati per migliorare la leggibilità.

alla fine finiscono con ciascuna linea essendo rappresentato come un elenco, con ogni elemento essendo una colonna che è spazio imbottiti in modo che le stesse colonne su ciascuna linea sono sempre la stessa lunghezza. Purtroppo quando ho effettivamente vai a stampa questa, non tutte le colonne in fila. Ho il sospetto che questo è a che fare con le sequenze di escape ASCII - perché la funzione len non sembra riconoscere questi:

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

E così mentre ogni colonna è la stessa lunghezza secondo len, essi non sono in realtà la stessa lunghezza quando vengono stampati sullo schermo.

C'è un modo (tranne che per fare un po 'hackery con le espressioni regolari che preferirei non fare) per prendere la stringa di escape e scoprire che cosa la lunghezza stampata è così che posso pad spazio in modo appropriato? Forse qualche modo a solo "Stampa" di nuovo a stringa ed esaminare la lunghezza di questo?

È stato utile?

Soluzione

Il wiki pyparsing include questo utile espressione per corrispondenza su sequenze di escape ANSI:

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

Ecco come fare questo in una via di fuga-sequenza-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)

stampe:

0.0 3

Altri suggerimenti

Non capisco due cose.

(1) E 'il vostro codice, sotto il vostro controllo. Si desidera aggiungere sequenze di escape per i vostri dati e poi li striscia fuori ancora una volta in modo da poter calcolare la lunghezza dei dati ?? Sembra molto più semplice per calcolare l'imbottitura prima aggiungendo le sequenze di escape. Che cosa mi manca?

presume Let che nessuna delle sequenze di escape cambiare la posizione del cursore. Se lo fanno, la risposta attualmente accettato non funziona comunque.

di supporre che vi siano i dati di stringa per ogni colonna Let (prima di aggiungere sequenze di escape) in un elenco di nome string_data e la larghezza delle colonne pre-determinato sono in un elenco di nome width. Provare qualcosa di simile:

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

Aggiorna-1

Dopo il commento di OP:

  

Il motivo che voglio mettere a nudo fuori e calcolare la lunghezza dopo il   stringa contiene i codici di colore è perché tutti i dati vengono costruiti   a livello di codice. Ho un sacco di metodi Colorize e sto costruendo   il backup dei dati o meno così: str = "%s/%s/%s" % (GREEN(data1), BLUE(data2), RED(data3)) Sarebbe piuttosto difficile da colorare il   testo dopo il fatto.

Se i dati è costruita da pezzi ciascuno con la propria formattazione, è ancora possibile calcolare la lunghezza visualizzata e pad a seconda dei casi. Ecco una funzione che fa che per i contenuti propria cella:

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

Se si pensa che la chiamata viene sovraccaricato dalla punteggiatura, si potrebbe fare qualcosa di simile:

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) non capisco il motivo per cui non si desidera utilizzare il kit di espressione regolare Python-fornito con-? No "aggiustamenti" (per ogni possibile significato di "aggiustamenti" che io sappia) è coinvolto:

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

Aggiorna-2

Dopo il commento di OP:

  

io preferisco ancora la risposta di Paolo dal momento che è più conciso

Più conciso di quello che? Non è la seguente espressione regolare abbastanza conciso soluzione per voi?

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

[Sopra codice risolto dopo @ Nick Perkins ha sottolineato che non ha funzionato]

ANSI_escape_code , la sequenza nel tuo esempio è Seleziona Graphic Rendition (probabilmente grassetto ).

Prova di posizionamento della colonna di controllo con il Posizione cursore (CSI n ; m H) sequenza. In questo modo, la larghezza del testo che precede non influisce sulla posizione colonna corrente e non c'è bisogno di preoccuparsi di larghezze di stringa.

Una soluzione migliore, se si target Unix, sta utilizzando la finestra maledizioni modulo -Oggetti . Ad esempio, una stringa può essere posizionato sullo schermo con:

  

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

     

Colore al massimo n caratteri della stringa str alla posizione (y, x) con attributi attr, sovrascrivendo qualsiasi cosa in precedenza sul display.

Se sei solo l'aggiunta di colore per alcune celle, è possibile aggiungere 9 per la larghezza della cella previsto (5 caratteri nascosti per accendere il colore, 4 per spegnerlo), ad esempio,

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

Dare

screenshot

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top