Obtenir la longueur de chaîne correcte en Python pour les chaînes avec des codes de couleur ANSI

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

  •  25-09-2019
  •  | 
  •  

Question

J'ai un code Python qui imprimera automatiquement un ensemble de données dans un format de colonne agréable, y compris en mettant dans les séquences d'échappement ASCII appropriées pour colorer divers morceaux des données pour une meilleure lisibilité.

Je termine finalement avec chaque ligne étant représenté sous la forme d'une liste, chaque élément étant une colonne qui est l'espace rembourré pour que les mêmes colonnes sur chaque ligne sont toujours la même longueur. Malheureusement, quand je vais en fait d'imprimer cela, pas toutes les colonnes alignées. Je soupçonne que cela est de faire avec les séquences d'échappement ASCII - parce que la fonction len ne semble pas reconnaître celles-ci:

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

Et tandis que chaque colonne est la même longueur selon len, ils ne sont pas en fait la même longueur lors de l'impression à l'écran.

Est-il possible (sauf pour faire une carriole avec des expressions régulières que je préfère ne pas faire) pour prendre la chaîne échappée et découvrir ce que la longueur imprimée est si je peux pad espace approprié? Peut-être un moyen de simplement « imprimer » revenir à chaîne et d'examiner la longueur de cela?

Était-ce utile?

La solution

Le wiki pyparsing inclut cette d'expression utile correspondant sur les séquences d'échappement ANSI:

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

Voici comment faire cela en une évasion séquence-strip-teaseuse:

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)

impressions:

0.0 3

Autres conseils

Je ne comprends pas deux choses.

(1) Il est votre code, sous votre contrôle. Vous voulez ajouter des séquences d'échappement à vos données, puis de les dépouiller à nouveau pour que vous puissiez calculer la longueur de vos données ?? Il semble beaucoup plus simple de calculer le rembourrage avant en ajoutant les séquences d'échappement. Qu'est-ce que je manque?

Voyons présumons que aucune des séquences d'échappement changer la position du curseur. Si oui, la réponse actuellement acceptée ne fonctionnera pas de toute façon.

Supposons que vous avez les données de chaîne pour chaque colonne (avant d'ajouter des séquences d'échappement) dans une liste nommée string_data et la largeur des colonnes prédéterminées sont dans une liste nommée width. Essayez quelque chose comme ceci:

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

Mise à jour-1

Après le commentaire de l'OP:

  

La raison pour laquelle je veux les dépouiller et calculer la longueur après la   chaîne contient les codes de couleur est parce que toutes les données se construit   programme. J'ai un tas de méthodes de Colorize et je construis   sauvegarder les données quelque chose comme ceci: str = "%s/%s/%s" % (GREEN(data1), BLUE(data2), RED(data3)) Il serait assez difficile de colorer la   texte après le fait.

Si les données est constitué de pièces chacune avec sa propre mise en forme, vous pouvez calculer la longueur affichée et pad, selon le cas. Voici une fonction qui fait que, pour le contenu d'une cellule:

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

Si vous pensez que l'appel est surchargée par la ponctuation, vous pouvez faire quelque chose comme:

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) Je ne comprends pas pourquoi vous ne voulez pas utiliser le kit d'expression régulière fourni avec Python? Non « hack » (pour tout sens possible de « carriole » que je suis au courant) est impliqué:

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

Mise à jour-2

Après le commentaire de l'OP:

  

Je préfère encore la réponse de Paul car il est plus concis

Plus concis que quoi? N'est pas la solution regex suivante assez concise pour vous?

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

[code ci-dessus corrigé après Perkins a souligné @ Nick que ça n'a pas marché]

Recherche ANSI_escape_code , la séquence dans votre exemple est Sélectionnez Graphique Rendition (probablement gras ).

Essayez de contrôler le positionnement de la colonne avec la séquence Position curseur (CSI n ; m H) . De cette façon, la largeur du texte qui précède ne modifie pas la position actuelle de la colonne et il n'y a pas besoin de se soucier des largeurs de chaîne.

Une meilleure option, si vous ciblez Unix, utilise le malédictions fenêtre du module -Objets . Par exemple, une chaîne peut être placée sur l'écran avec:

  

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

     

Peinture au plus n caractères de la chaîne str à (y, x) avec attr attributs, tout en écrasant précédemment sur l'écran.

Si vous êtes juste ajouter de la couleur à certaines cellules, vous pouvez ajouter 9 à la largeur de la cellule attendue (5 caractères cachés pour activer la couleur, 4 pour la désactiver), par exemple

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

Donner

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top