pyqt5优秀项⽬python_Python优秀开源项⽬Rich源码解析这篇⽂章对优秀的开源项⽬Rich的源码进⾏解析,OMG,盘他。为什么建议阅读源码,有两个原因,第⼀,单纯学语⾔很难在实践中灵活应⽤,通过阅读源码可以看到每个知识点的运⽤场景,印象会更深,以后写代码的时候就能应⽤起来;第⼆,通过阅读优秀的开源代码,可以学习⽐⼈的代码规范、设计思路;第三,参与到开源社区,获得更⼴阔的的发展前景;第四,⾯试加分项。所以,有时间的话还是建议⼤家多读读优秀开源项⽬的源码。
各种格式
进度条
效果看起来很酷炫,我忍不住看了⼀些代码,发现作者⽤的是Python 3.8版本实现的,好多新特性我也不了解,所以在看源码过程中还补了⼀下语法基础。下⾯以⼀个例⼦来简单看看Rich的源码,源码的讲解我尽量⾔简意赅,重点讲解源码中涉及的⼀些关键的知识点。
先捡个软柿⼦捏,如下:
from rich import print
print('Hello, [bold yellow]World[/bold yellow]!')
输出效果:
可以看到对单词World显⽰为粗体、红颜⾊。
先通过⼀张图来看看⼤致流程
简单来说就是将⽂本的格式转化成标准输出能够识别的格式,然后输出即可。下⾯来讲解源码,当我们调⽤print函数时,最终程序会跳转到console.py⽂件的print函数中,执⾏以下代码
调⽤self._collect_renderables函数处理输⼊的字符串,将需要格式化的部分标出来,返回的renderables变量是⼀个Text列表,因为输⼊只有1个字符串,所以列表的⼤⼩为1,变量结果如下源代码下载开源社区
Span(7, 12, 'bold red')便是框出来需要格式化的内容。
上述代码还有⼀个with self,它的作⽤我们⼀会⼉再说。接着print函数往下看
这⾥会遍历刚刚提到的renderables变量,先调⽤render函数渲染输⼊的⽂本,然后调⽤extend函数将render返回的结果添加到self._buffer列表⾥。这⾥有⼏个知识点简单说⼀下
self._buffer是函数调⽤,由于它加了@property注解,所以调⽤是可以不⽤加⼩括号,它返回的是self._thread_locals.buffer变量,该变量是List[Segment]类型的
self._thread_locals.buffer变量⽤到dataclasses模块的field函数初始化,初始化代码为buffer: List[Segment] =
field(default_factory=list),dataclasses是Python 3.7 版本的新引⼊的模块,field函数可提供更加灵活的初始化⽅式,并且该模块中的@dataclass注解可以为类⾃动添加__init__等⽅法,⽐较⽅便
extend = self._d这种写法将list的extent函数存到了临时变量⾥,后续直接通过extend调⽤该函数,⽐对象名.extend的⽅式更简洁。
下⾯我们来看render(renderable, render_options)函数的渲染逻辑,该函数⾥会调⽤下⾯的代码
render_iterable = renderable.__rich_console__(self, options)
在函数声明⾥renderable对象是RenderableType类型的,但实际上Text类型的,并且这两种类型没有继承关系,这⾥没太想明⽩作者为什么这样搞。所以,这⾥的__rich_console__函数我们要到text.py⽂件中去。__rich_console__函数最终会调⽤Text对象的render函数,核⼼代码如下:
def render(self, console: "Console", end: str = "") -> Iterable["Segment"]:
style_map = {index: get_style(span.style) for index, span in enumerated_spans}
_Segment = Segment
for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]):
yield _Segment(text[offset:next_offset], get_current_style())
调⽤get_style函数,将格式转为Style对象,如:'bold red'转成Style对象,然后按照不同的显⽰格式进⾏‘分⽚’,每个‘⽚段’构造⼀
个Segment对象存储⽂本及其对应的格式。
get_style函数会调⽤Style.parse(name)⽣成Style对象,核⼼代码如下
@lru_cache(maxsize=1024)
def parse(cls, style_definition: str) -> "Style":
words = iter(style_definition.split())
for original_word in words:
word = original_word.lower()
if word == "on":
# ...省略
elif word in style_attributes:
attributes[style_attributes[word]] = True
else:
color = word
style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
return style
参数style_definition取值为bold red,分割后⽣成['bold', 'red']列表,当word变量等于'bold'时,会执⾏attributes[style_attributes[word]] = True语句,执⾏后attributes等于{'bold': true},它是⼀个字典。当word变量等于red时,执⾏color=word语句。最终调⽤导数第⼆⾏构
造Style对象,Style对象最核⼼的两个数据形式_attributes和_color, 前者是int类型,在我们例⼦中取值是1,代表'bold',即:粗体。后者代表颜⾊,即:'red',它是Color类型的,该类中有个属性number也是我们后续要⽤到的。
下⾯来看下__rich_console__函数返回了哪些Segment对象
可以看到有4个,每⼀个都有⽂本及其Style对象。
回到render(renderable, render_options)函数,刚刚介绍了__rich_console__部分,下⾯还有返回的代码, ⼀起来看看
iter_render = iter(render_iterable)
for render_output in iter_render:
if isinstance(render_output, Segment):
yield render_output
render_iterable变量是__rich_console__的返回值,即:4个Segment对象。遍历后通过yield⽅式返回。该关键字⽤来返回⼀个迭代器,也可以理解为⼀个列表。并且yield返回有个特点,函数返回值只有真正被使⽤的时候才会执⾏调⽤函数。
这样,render(renderable, render_options)函数就讲解完了,返回上⼀层extend(render(renderable, render_options)),通过extend函数将4个Segment对象保存到buffer中,结果如下
然后print⽅法就执⾏完了。看起来已经结束了,然⽽控制台打印的代码貌似没有看到。答案就在刚刚的with self中,with关键字使得执⾏完代码体后,会⾃动调⽤self的__exit__函数。__exit__函数中调⽤_render_buffer函数进⾏最终的输出,核⼼代码如下
output: List[str] = []
append = output.append
for line in Segment.split_and_crop_lines(buffer, self.width, pad=False):
for text, style, is_control in line:
if style and not is_control:
append(
text,
color_system=color_system,
legacy_windows=legacy_windows,
)
)
rendered = "".join(output)
return rendered
split_and_crop_lines函数是为了适应控制台的宽度,暂时忽略它。line变量仍然是刚刚提到的4个Segment对象,通过for text, style,
is_control in line直接将每个Segment对象的属性解出来并赋给text, style, is_control变量,最终每个style对象都会调⽤render⽅法完成最后的渲染。
render⽅法核⼼代码如下
attrs = self._make_ansi_codes(color_system)
rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
_make_ansi_codes函数就不展开了, 其实就是利⽤上⾯提到的_attributes和number属性⽣成标准输出的能够识别的格式,返回值attrs的结果为1;31,1取⾃_attributes代表粗体,31中的1取⾃number代表颜⾊,其他颜⾊取值是不同的,⽐如黄⾊是33,紫⾊是35。最后通过f-string格式(新特性)⽣成rendered变量,取值为[1;31mWorld[0m它就是标准输出流能够识别的格式。
回到_render_buffer函数中,调⽤rendered = "".join(output)将4个渲染后的⽚段拼在⼀起,返回。返回后执⾏的代码如下:
text = self._render_buffer()
if text:
self.file.write(text)
self.file变量的赋值语句为self.file = file or sys.stdout,由于我们没有定义file变量,所以self.file取值为sys.stdout。最终的输出
为sys.stdout.write(text),⾄此整个流程就讲解完了。如果你理解了上述逻辑,应该可以通过下⾯代码输出同样的效果
sys.stdout.write('Hello, \033[1;31mWorld\033[0m!')
所以Rich做的就是把⽂字格式准成标准输出流能识别的格式。
Rich⾥⽤到的代码确实挺新的,能学到很多东西,⽐直接看书来的快,有兴趣的朋友可以⾃⾏阅读。经常读我⽂章的朋友知道,我⼀直在寻新的内容、新⽅向,这次源码解析也是⼀次新的尝试,不知道是不是⼀件有价值的事情,先持续更新⼏篇看看。如果你觉得有⽤也想看更多的源码解析的⽂章,希望点个赞或者在看⿎励⼀下,不胜感激。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。