我有一些Python代码会以一种漂亮的列格式自动打印一组数据,包括输入适当的ASCII转义序列来为各种数据着色以便于阅读。
我最终将每一行表示为一个列表,每个项目都是一个空格填充的列,以便每行上的相同列始终具有相同的长度。不幸的是,当我真正去打印时,并非所有列都排成一行。我怀疑这与ASCII转义序列有关 - 因为len
函数似乎无法识别这些:
>>> a = '\x1b[1m0.0\x1b[0m'
>>> len(a)
11
>>> print a
0.0
因此,虽然根据len
每列的长度相同,但在屏幕上打印时它们的长度实际上并不相同。
有没有办法(除了用正则表达式做一些hackery,我宁愿不这样做)来获取转义字符串并找出打印长度是什么,所以我可以适当地填充空间?也许某种方式只是将它“打印”回字符串并检查它的长度?
答案 0 :(得分:11)
pyparsing wiki包含此helpful expression,用于匹配ANSI转义序列:
ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) +
oneOf(list(alphas)))
以下是如何将其转换为转义序列剥离器:
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 :(得分:3)
我不明白两件事。
(1)这是你的代码,在你的控制之下。您想要将转义序列添加到数据中,然后再将它们删除,以便您可以计算数据的长度?在添加转义序列之前计算填充似乎更简单。我错过了什么?
假设没有一个转义序列改变了光标位置。如果他们这样做,目前接受的答案无论如何都不会起作用。
假设您在名为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
OP发表评论后:
我想将它们剥离并计算出之后的长度的原因 string包含颜色代码是因为所有数据都是建立起来的 编程。我有一堆着色方法,我正在建设 这样的数据是这样的:
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正则表达式套件?没有“hackery”(对于我所知道的“hackery”的任何可能含义):
>>> 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
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指出它无法正常工作后更正]
答案 2 :(得分:1)
查看ANSI_escape_code,示例中的序列是 选择图形再现(可能粗体)。
尝试使用光标位置(CSI n ; m H
)序列控制色谱柱定位。
这样,前面文本的宽度不会影响当前列的位置,也不需要担心字符串宽度。
如果您定位Unix,更好的选择是使用curses module window-objects。 例如,字符串可以在屏幕上显示:
window.addnstr([y, x], str, n[, attr])
使用属性attr绘制字符串str(y,x)的最多n个字符,覆盖之前显示的任何内容。
答案 3 :(得分:1)
如果您只是为某些单元格添加颜色,则可以将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))
给予