Rich Text 用负下标取字丢样式,改用规范化位置
Rich 15.0.0 的负整数下标能取对字符却丢掉样式 spans。通过带边界检查的位置转换或符合业务语义的切片规避,并核对重叠样式。
Rich 的 Text 用 text[-1] 取最后一个字,字符正确,颜色或加粗却没了;换成对应的正下标,样式又回来。Rich 15.0.0 中,负整数下标没有在匹配样式区间前转换为非负位置,会出现这种“内容相同、spans 不同”的结果。
问题不依赖终端能否显示 ANSI 颜色。Text.from_markup("[bold]hello[/bold]") 的 text[4] 带有加粗 span,text[-1] 却可能没有;打印两者的 spans 就能区分对象内的数据与最终终端渲染。
把有效负下标转换后再取字
如果自己的代码需要任意整数下标,可以在封装边界规范化位置,并显式保留越界错误:
from rich.text import Text
def styled_char(text: Text, index: int) -> Text:
position = index if index >= 0 else len(text) + index
if position < 0 or position >= len(text):
raise IndexError("Text index out of range")
return text[position]
这是对 Rich 15.0.0 行为的局部兼容函数,输入约定为整数下标,不接受切片。它让样式检查使用与字符串取字一致的非负位置;没有直接修改 Rich 的私有 span 数组。
不能只写 text[len(text) + index] 后完全省略边界检查。若 index 比负长度还小,换算结果仍是负数,可能再次进入 Python 的负下标语义,错误地返回另一个字符。空文本和正向越界同样应继续抛出异常。
只取末尾一段时,也可用切片
Rich 15.0.0 的步长为 1 的切片会先通过 slice.indices() 规范化范围,因此 text[-1:] 可以保留最后一个字符已有的样式。需要注意,空文本切片返回空 Text,而 text[-1] 会报 IndexError;如果业务要求空输入必须失败,不能悄悄用切片改变这项约定。
这里的位置按 Python 字符串索引计算,不是终端显示格数。中文、组合字符和 emoji 的显示宽度可能不同;将终端列号直接当字符串下标,是另外一项坐标转换问题。
截至 2026 年 10 月 26 日,正式 Rich 15.0.0 的整数分支仍直接拿原下标比较非负 span 边界。后续升级后应使用相同断言核对,再决定是否移除封装。
复核包含正负对应位置、重叠样式、没有样式的字符、空文本和越界。除了 .plain,还要比较样式区间及顺序,确认没有把两层样式压成一层。若最终打印仍无颜色,再检查 Console 与输出终端的颜色设置;那不会恢复一个已经在取字时丢掉的 span。


