Python优秀开源项目Rich源码解析
这篇文章对优秀的开源项目Rich
的源码进行解析,OMG,盘他。为什么建议阅读源码,有两个原因,第一,单纯学语言很难在实践中灵活应用,通过阅读源码可以看到每个知识点的运用场景,印象会更深,以后写代码的时候就能应用起来;第二,通过阅读优秀的开源代码,可以学习比人的代码规范、设计思路;第三,参与到开源社区,获得更广阔的的发展前景;第四,面试加分项。所以,有时间的话还是建议大家多读读优秀开源项目的源码。
下面进入今天的主题,这个开源项目的名字叫Rich
,地址:https://github.com/willmcgugan/rich (可以点击文末阅读原文
查看)。 这个项目是个英国老铁开发的,比较友好的是有中文文档。它的作用是可以在控制台输出富文本和精美的可视化格式(如:表格、进度条和markdown)。截图感受一下
效果看起来很酷炫,我忍不住看了一些代码,发现作者用的是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._buffer.extend
这种写法将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(
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
函数就不展开了, 其实就是利用上面提到的_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
里用到的代码确实挺新的,能学到很多东西,比直接看书来的快,有兴趣的朋友可以自行阅读。欢迎关注公众号**渡码**不断分享优秀开源项目源码分析
Python优秀开源项目Rich源码解析的更多相关文章
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析
通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...
- 开源项目Telegram源码 Telegram for Android Source
背景介绍 Telegram 是一款跨平台的即时通信软件,它的客户端是自由及开放源代码软件.用户可以相互交换加密与自毁消息,发送照片.影片等所有类型文件.官方提供手机版.桌面版和网页版等多种平台客户端. ...
- 10个经典的Android开源项目(附源码包)
最近在抽空学习Android系统开发,对Android学习也比较感兴趣,刚开始学就试着在网上找几个项目源码研究看下,以下就将找到的Android项目源码列出,希望对正在或准备学习Android系统开发 ...
- Android 网络流量监听开源项目-ConnectionClass源码分析
很多App要做到极致的话,对网络状态的监听是很有必要的,比如在网络差的时候加载质量一般的小图,缩略图,在网络好的时候,加载高清大图,脸书的android 客户端就是这么做的, 当然伟大的脸书也把这部分 ...
- 开源地图SharpMap源码解析-(1)简介
1.简介 SharpMap最新版基于.NET Framework 4,采用C#开发的地图渲染引擎,非常易于使用.我这次研究的是比较稳定发布的V1.1版本.可以在GitHub下载该源码,地址:https ...
- 转帖:向开源项目贡献源码(以 Orchard 为例)
原文地址:http://yangw80.blog.163.com/blog/static/247518002201552692516908/ 在开源项目满天飞的时代,仅仅把开源项目拿来用是不够的,要适 ...
随机推荐
- 实战记录之SQL server报错手工注入
前言 最近测试了一个站点,这个站点挺有意思,发现没有关闭错误提示,初步猜测是SQL server数据库,后来验证确实是.在这里记录一下实战过程,并详细讲解一下用到的知识点. SQL server报错注 ...
- 开发者大赛 | aelf轻型DApp开发训练大赛结果公布!
6月9日,由aelf基金会发起的轻型DApp开发训练大赛圆满收官.本次训练赛基于aelf公开测试网展开,主要针对轻型DApp,旨在激励更多的开发者参与到aelf生态中来. 活动于4月21日上线后,ae ...
- 面试了 6 轮 Google 中国 之后,还是挂了
去年换工作的时候, 面试了一下 Google (这里说的是 Google 中国哈), 来了个 Google 面试六轮游, 结果是没通过.
- vs2010静态编译qt5.1.0
本博文参考 http://blog.chinaunix.net/uid-20690340-id-3802197.html 静态库在链接的时候直接写入二进制文件里,这样的好处在于发布的时候无需附带dll ...
- Laravel:No application encryption key has been specified.
其实吧,这个就是你没有生成密钥 你首先去看看,如果是刚刚下载的lavavel应该会有一个.env.example文件在根目录下,然后修改这个文件名,改成.env 然后用命令行去执行php artisa ...
- [问题解决]Windows下python中pydoc命令提示“'pydoc' 不是内部或外部命令,也不是可运行的程序 或批处理文件。”
解决方法:python -m pydoc 例:python -m pydoc print
- IOS App破解之路一 拿到appstore上的ipa
1, 在Mac电脑上的app store里搜索Apple Configurator2 并安装 2, iPhone手机连接Mac电脑 3, 登录Apple Configurator2 菜单栏, 账号 ...
- 键盘侠Linux教程(四)| 常用命令
前言 Linux命令并不可怕,只要熟悉日常的操作命令即可,其他不熟悉的命令,需要用到的时候可以查阅资料,熟能生巧. Linux常用操作命令 命令的基本格式 命令的提示符 [root@localhost ...
- Python中用OpenPyXL处理Excel表格 - 单元格格式设置
官方文档: http://openpyxl.readthedocs.io/en/default/ OpenPyXL库 --单元格样式设置 单元格样式的控制,依赖openpyxl.style包,其中定义 ...
- 键盘侠Linux干货| ELK(Elasticsearch + Logstash + Kibana) 搭建教程
前言 Elasticsearch + Logstash + Kibana(ELK)是一套开源的日志管理方案,分析网站的访问情况时我们一般会借助 Google / 百度 / CNZZ 等方式嵌入 JS ...