Python优秀开源项目Rich源码解析的流程分析


Posted in Python onJuly 06, 2020

这篇文章对优秀的开源项目Rich的源码进行解析,OMG,盘他。为什么建议阅读源码,有两个原因,第一,单纯学语言很难在实践中灵活应用,通过阅读源码可以看到每个知识点的运用场景,印象会更深,以后写代码的时候就能应用起来;第二,通过阅读优秀的开源代码,可以学习比人的代码规范、设计思路;第三,参与到开源社区,获得更广阔的的发展前景;第四,面试加分项。所以,有时间的话还是建议大家多读读优秀开源项目的源码。

下面进入今天的主题,这个开源项目的名字叫Rich,地址:https://github.com/willmcgugan/rich 。这个项目是个英国老铁开发的,比较友好的是有中文文档。它的作用是可以在控制台输出富文本和精美的可视化格式(如:表格、进度条和markdown)。截图感受一下

Python优秀开源项目Rich源码解析的流程分析

各种格式

Python优秀开源项目Rich源码解析的流程分析

进度条

效果看起来很酷炫,我忍不住看了一些代码,发现作者用的是Python 3.8版本实现的,好多新特性我也不了解,所以在看源码过程中还补了一下语法基础。下面以一个例子来简单看看Rich的源码,源码的讲解我尽量言简意赅,重点讲解源码中涉及的一些关键的知识点。

先捡个软柿子捏,如下:

from rich import print

print('Hello, [bold yellow]World[/bold yellow]!')

 

输出效果:

Python优秀开源项目Rich源码解析的流程分析

可以看到对单词World显示为粗体、红颜色。

先通过一张图来看看大致流程

Python优秀开源项目Rich源码解析的流程分析

简单来说就是将文本的格式转化成标准输出能够识别的格式,然后输出即可。下面来讲解源码,当我们调用print函数时,最终程序会跳转到console.py文件的print函数中,执行以下代码

Python优秀开源项目Rich源码解析的流程分析

调用self._collect_renderables函数处理输入的字符串,将需要格式化的部分标出来,返回的renderables变量是一个Text列表,因为输入只有1个字符串,所以列表的大小为1,变量结果如下

Python优秀开源项目Rich源码解析的流程分析

Span(7, 12, 'bold red')便是框出来需要格式化的内容。

上述代码还有一个with self,它的作用我们一会儿再说。接着print函数往下看

Python优秀开源项目Rich源码解析的流程分析

这里会遍历刚刚提到的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)dataclassesPython 3.7 版本的新引入的模块,field函数可提供更加灵活的初始化方式,并且该模块中的@dataclass注解可以为类自动添加__init__等方法,比较方便
  • extend = self._buffer.extend这种写法将listextent函数存到了临时变量里,后续直接通过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对象

Python优秀开源项目Rich源码解析的流程分析

可以看到有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中,结果如下

Python优秀开源项目Rich源码解析的流程分析

然后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(
  style.render(
   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函数就不展开了, 其实就是利用上面提到的_attributesnumber属性生成标准输出的能够识别的格式,返回值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里用到的代码确实挺新的,能学到很多东西,比直接看书来的快,有兴趣的朋友可以自行阅读。

源码:https://github.com/willmcgugan/rich

总结

到此这篇关于Python优秀开源项目Rich源码解析的文章就介绍到这了,更多相关Python优秀开源项目内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python实现从URL地址提取文件名的方法
May 15 Python
深入解读Python解析XML的几种方式
Feb 16 Python
详解python的ORM中Pony用法
Feb 09 Python
python模块smtplib学习
May 22 Python
python通过配置文件共享全局变量的实例
Jan 11 Python
Python零基础入门学习之输入与输出
Apr 03 Python
PyQt5根据控件Id获取控件对象的方法
Jun 25 Python
python把ipynb文件转换成pdf文件过程详解
Jul 09 Python
Django中提示消息messages的设置方式
Nov 15 Python
python如何求100以内的素数
May 27 Python
Django Session和Cookie分别实现记住用户登录状态操作
Jul 02 Python
Python实现打包成库供别的模块调用
Jul 13 Python
使用TensorBoard进行超参数优化的实现
Jul 06 #Python
Django中F函数的使用示例代码详解
Jul 06 #Python
Python 实现 T00ls 自动签到脚本代码(邮件+钉钉通知)
Jul 06 #Python
Django-imagekit的使用详解
Jul 06 #Python
大数据分析用java还是Python
Jul 06 #Python
python文件操作seek()偏移量,读取指正到指定位置操作
Jul 05 #Python
python 读txt文件,按‘,’分割每行数据操作
Jul 05 #Python
You might like
php实现的一个简单json rpc框架实例
2015/03/30 PHP
64位windows系统下安装Memcache缓存
2015/12/06 PHP
PHP使用http_build_query()构造URL字符串的方法
2016/04/02 PHP
JavaScript 字符串乘法
2009/08/20 Javascript
基于jQuery的图片左右无缝滚动插件
2012/05/23 Javascript
禁用键盘上的(全局)指定键兼容iE、Chrome、火狐
2013/05/14 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery获取select选中的option的value值实现方法
2016/08/29 Javascript
详解探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 构建记事本应用
2017/06/16 Javascript
基于LayUI分页和LayUI laypage分页的使用示例
2017/08/02 Javascript
详解使用vscode+es6写nodejs服务端调试配置
2017/09/21 NodeJs
React-Native之定时器Timer的实现代码
2017/10/04 Javascript
详解ES6 export default 和 import语句中的解构赋值
2019/05/28 Javascript
微信小程序用canvas画图并分享
2020/03/09 Javascript
微信小程序利用button控制条件标签的变量问题
2020/03/15 Javascript
python运行时间的几种方法
2016/06/17 Python
Python实现获取命令行输出结果的方法
2017/06/10 Python
python获取程序执行文件路径的方法(推荐)
2018/04/26 Python
使用Python刷淘宝喵币(低阶入门版)
2019/10/30 Python
Python如何给你的程序做性能测试
2020/07/29 Python
20行Python代码实现一款永久免费PDF编辑工具的实现
2020/08/27 Python
HTML5等待加载动画效果
2017/07/27 HTML / CSS
世界领先的高品质定制产品平台:Zazzle
2017/07/23 全球购物
2019史上最全Database工程师题库
2015/12/06 面试题
EJB需直接实现它的业务接口或Home接口吗,请简述理由
2016/11/23 面试题
个人自我鉴定怎么写
2013/10/28 职场文书
毕业自我评价范文
2013/11/17 职场文书
先进集体事迹材料
2014/02/17 职场文书
贯彻落实“八项规定”思想汇报
2014/09/13 职场文书
四风问题个人对照检查剖析材料
2014/09/27 职场文书
2016大学生毕业实习心得体会
2016/01/23 职场文书
2019个人工作态度自我评价
2019/04/24 职场文书
再读《皇帝的新衣》的读后感悟!
2019/08/07 职场文书
高一作文之暖冬
2019/11/09 职场文书
Go 实现英尺和米的简单单位换算方式
2021/04/29 Golang
Go 通过结构struct实现接口interface的问题
2021/10/05 Golang