2010-02-02 11 views
16

Ho un codice Python che stamperà automaticamente una serie di dati in un formato di colonna piacevole, incluso inserire le sequenze di escape ASCII appropriate per colorare vari pezzi del dati per la leggibilità.Ottenere la lunghezza della stringa corretta in Python per stringhe con codici colore ANSI

Alla fine, ogni riga viene rappresentata come un elenco, ogni elemento è una colonna riempita di spazio in modo che le stesse colonne su ogni riga abbiano sempre la stessa lunghezza. Sfortunatamente quando vado effettivamente a stampare questo, non tutte le colonne si allineano. 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 in base alle len, non sono in realtà la stessa lunghezza quando stampato sullo schermo.

Esiste un modo (ad esempio, se non si fa un po 'di hackery con espressioni regolari che preferisco non fare) per prendere la stringa di escape e scoprire qual è la lunghezza stampata in modo da poter utilizzare lo space pad in modo appropriato? Forse un modo per "stamparlo" di nuovo sulla corda ed esaminarne la lunghezza?

+3

Questi sono effettivamente "ANSI" codici colore, non "ASCII", come sarebbe mostrato su un terminale ANSI colore, o su un PC mediante il driver ANSI.SYS. – PaulMcG

risposta

9

Il wiki pyparsing include questo helpful expression 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 
+0

Tecnicamente l'elenco delimitato può contenere anche delle stringhe, anche se è improbabile che tu possa mai incontrare una tale sequenza. Vedi anche http://stackoverflow.com/questions/1833873/python-regex-escape-characters/1834669#1834669 – bobince

+1

OH, non lo so! Nella mia giovinezza, abbiamo fatto ballare quei VT100, facendo lampeggiare i loro LED, cambiando le loro regioni di scorrimento, emettendo caratteri double-high-double-wide, in grassetto inverso - ah, che giorni inebrianti quelli erano ... – PaulMcG

+0

Grazie, ha funzionato perfettamente ! Speravo che ci fosse solo un metodo di blahlibrary.unescape() da qualche parte che stavo trascurando, ma questa è la prossima cosa migliore! –

1

Guardando in ANSI_escape_code, la sequenza nel vostro esempio le è Seleziona Graphic Rendition (probabilmente grassetto).

Provare a controllare il posizionamento della colonna con la sequenza CUrsor Position (CSI n ; m H). In questo modo, la larghezza del testo precedente non influisce sulla posizione corrente della colonna e non è necessario preoccuparsi delle larghezze delle stringhe.

Un'opzione migliore, se si targetizza Unix, utilizza lo curses module window-objects. Ad esempio, una stringa può essere posizionato sullo schermo con:

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

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

+0

Grazie - darò un'occhiata alle maledizioni. –

3

Non capisco DUE cose.

(1) È il tuo codice, sotto il tuo controllo. Vuoi aggiungere sequenze di escape ai tuoi dati e poi eliminarli nuovamente in modo da poter calcolare la lunghezza dei tuoi dati ?? Sembra molto più semplice calcolare il padding prima del aggiungendo le sequenze di escape. Cosa mi manca?

Supponiamo che nessuna delle sequenze di escape cambi la posizione del cursore. Se lo fanno, la risposta attualmente accettata non funzionerà comunque.

Supponiamo di avere i dati di stringa per ogni colonna (prima di aggiungere sequenze di escape) in un elenco denominato string_data e le larghezze di colonna predeterminate si trovano in un elenco denominato 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)) 

Aggiornamento dopo il commento del PO "" "Il motivo che voglio mettere a nudo fuori e calcolare la lunghezza dopo la stringa contiene i codici di colore è dovuto al fatto che tutti i dati è costruito a livello di codice. Ho un sacco di metodi di colorizzazione e sto costruendo i dati qualcosa del genere: str = "% s /% s /% s"% (VERDE (dati1), BLU (dati2), ROSSO (dati3)) Sarebbe essere abbastanza difficile da colorare il testo dopo il fatto. "" "

Se i dati sono costituiti da pezzi ciascuno con la propria formattazione, è ancora possibile calcolare la lunghezza e il pad visualizzati, a seconda dei casi. Ecco una funzione che fa che per i contenuti una 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 perché non si desidera utilizzare il kit di espressioni regolari fornito con Python. 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 
>>> 

Aggiornamento dopo il commento del PO "" "Continuo a preferire la risposta di Paolo dal momento che è più conciso"" "

Più conciso che cosa? La soluzione regex non è abbastanza concisa 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 corretto dopo @ Nick Perkins ha sottolineato che non ha funzionato]

+0

Grazie per la risposta John. Il motivo per cui voglio eliminarli e calcolare la lunghezza * dopo * la stringa contiene i codici colore è perché tutti i dati sono creati a livello di codice. Ho un sacco di metodi di colorizzazione e sto costruendo i dati qualcosa del genere: str = "% s /% s /% s"% (VERDE (dati1), BLU (dati2), ROSSO (dati3)) Sarebbe piuttosto difficile colorare il testo dopo il fatto.Per quanto riguarda l'hackery, forse quello che avrei dovuto dire era: "Immagino che questo sia un problema risolto e non riesco proprio a trovare la libreria giusta". Indovinate, ma preferisco ancora la risposta di Paul poiché è più concisa. –

+0

OK, morderò su questo. Vedo che cosa stai facendo calcolando la lunghezza mentre vai. La soluzione che proponi non funziona del tutto, solo perché conoscere la lunghezza della colonna desiderata prima del tempo richiede che tu conosca la lunghezza della stringa più grande da memorizzare in una cella di quella colonna. Ciononostante, non penso sarebbe troppo difficile scrivere qualcosa che ha aggirato questo problema e non ha richiesto di rimuovere le sequenze di colore dopo il fatto. –

+1

Per quanto riguarda il commento regex, non ho alcun problema nell'usare il supporto integrato di Python. Solitamente tendo a evitare di eseguire l'analisi regex perché è facile incasinare e dimenticare un caso limite. Vedi la lista infinita di domande qui su SO da persone che cercano di usare espressioni regolari con HTML per dimostrarlo. Oppure, guarda il commento sul post di Paul, che sottolinea che ciò che ha fornito in realtà non tiene conto dei codici di controllo non colore. Detto questo, quando si preoccupano solo dei colori, è piuttosto semplice come hai mostrato. –

0

Se sei solo l'aggiunta di colore per alcune cellule, è più semplice per aggiungere solo 9 per la larghezza della cella previsto (5 nascosto caratteri per accendere il colore, 4 per disattivarlo), ad es

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