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)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对象

可以看到有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函数就不展开了, 其实就是利用上面提到的_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优秀开源项目内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 微信小程序rich-text富文本用法实例分析

    本文实例讲述了微信小程序rich-text富文本用法.分享给大家供大家参考,具体如下: rich-text是一个新增的微信小程序插件,从基础库1.4.0开始,低版本需要做兼容处理 nodes属性可为Array和String类型,但推荐使用Array.由于String类型最终也会转为Array类型 nodes分为支持两种节点,分别为元素节点(type=node ,默认为元素节点)和文本节点(type=text) 元素节点 name 标签名 String 是 支持部分受信任的HTML节点 attrs

  • Android RichText 让Textview轻松的支持富文本(图像ImageSpan、点击效果等等类似QQ微信聊天)

    AndroidRichText帮助实现像QQ,微信一样的,一个TextView里既有文字又有表情又有图片的效果,采用插件化的框架,代码简单,可拓展性强. 基础框架包只有四个java文件, RichTextWrapper :TextView的包裹类,实现支持富文本,通过new RichTextWrapper(TextView v)来构造. RTMovementMethod: 继承自Android原生的LinkMovementMethod,重写onTouchEvent方法,优化了ClickSpan(

  • 小程序rich-text组件如何改变内部img图片样式的方法

    一.起因 小程序中有一个页面,很奇葩,是通过后端传过来的整段HTML字符串展示内容的,那么我们暂时叫这个页面为content,传过来之后,他里面的图片样式是不固定的,有的大,有的小,有的有style有的没有,那我怎么能让他统一展示成一样的样式呢? 二.办法 思前想后,我觉得还是正则比较靠谱,把content里面所有的img标签筛选出来,把里面有style的.有width的.有height的.全部删除掉,最后在统一加上我们想要的样式,最终代码如下: <RichText nodes={content

  • 微信小程序 rich-text的使用方法

     微信小程序 rich-text的使用方法 rich-text 属性:nodes 类型:Array / String 结点列表 / HTML String 全局支持class和style属性,不支持id属性. 结点类型:type = node , name 标签名 String 是 支持部分受信任的HTML结点,  attrs 属性 Object 否 支持部分受信任的属性,遵循Pascal命名法 ,  children 子结点列表 Array 否 结构和nodes一致 结点类型:type = t

  • C#中richtextbox使用方法详解

    C#中RichTextBox使用方法和TextBox基本一样,只不过RichText除了TXT外,还支持RTF格式的文档.本文详细介绍RichTextBox的使用方法供大家参考,具体如下: 一.RichTextBox的使用方法 RichTextBox.Find方法 RichTextBox控件不仅允许输入和编辑文本,同时还提供了标准 TextBox 控件未具有的.更高级的指定格式的许多功能. 语法:RichTextBox 说明: RichTextBox 提供了一些属性,对于本控件文本的任何部分,用

  • 新手如何发布Python项目开源包过程详解

    本文假设你在 GitHub 上已经有一个想要打包和发布的项目. 第 0 步:获取项目许可证 在做其他事之前,由于你的项目要开源,因此应该有一个许可证.获取哪种许可证取决于项目包的使用方式.开源项目中一些常见许可证有 MIT 或 BSD. 要在项目中添加许可证,只需参照以下链接中的步骤,将 LICENSE 文件添加到项目库中的根目录即可:  https://help.github.com/en/articles/adding-a-license-to-a-repository 第 1 步:让你的代

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

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

  • 学习php开源项目的源码指南

    一.先把源代码安装起来,结合它的文档和手册,熟悉其功能和它的应用方式. 二.浏览源代码的目录结构,了解各个目录的功能. 三.经过以上两步后相信你对这个开源的产品有了一个初步的了解了,那现在就开始分析它的源码吧.这一步我们开始分析源代码框架.例如入口方式是单入口还是多入口,页面之间的调用规则,能根据规则找出某个功能用到的页面. 四.熟悉源代码的代码写作风格,例如缩进方式,排版格式等. 五.熟悉一下源代码用到的数据库和表,可以参考它的技术支持文档. 六.经过以上几步相信大家已经对这份源代码有了更深刻

  • 微信跳一跳python辅助软件思路及图像识别源码解析

    本文将梳理github上最火的wechat_jump_game的实现思路,并解析其图像处理部分源码 首先废话少说先看效果 核心思想 获取棋子到下一个方块的中心点的距离 计算触摸屏幕的时间 点击屏幕 重要方法 计算棋子到下一个方块中心点的距离 使用 adb shell screencap -p 命令获取手机当前屏幕画面 再通过图像上的信息找出棋子的坐标和下一个方块中心点的坐标 然后通过两点间距离公式计算出距离 计算触摸屏幕的时间 T=A * S 其中S为上步算出的像素距离,T为按压时间(ms),A

  • Python中getpass模块无回显输入源码解析

    本文主要讨论了python中getpass模块的相关内容,具体如下. getpass模块 昨天跟学弟吹牛b安利Python标准库官方文档的时候偶然发现了这个模块.仔细一看内容挺少的,只有两个主要api,就花了点时间阅读了一下源码,感觉挺实用的,在这安利给大家. getpass.getpass(prompt='Password: ', stream=None) 调用该函数可以在命令行窗口里面无回显输入密码.参数prompt代表提示字符串,默认是'Password: '.在Unix系统中,strea

  • python wsgiref源码解析

    python web开发中http请求的处理流程通常是: web-browser , web-server , wsgi 和 web-application四个环节, 我们学习过基于bottle实现的web-application,也学习了http.server.再完成python3源码中自带的wsgiref的库,就可以拼接最后一个环节wsgi.本文会分下面几个部分: wsgi相关概念 cgi示例 wsgiref源码 wsgi小结 小技巧 wsgi 相关概念 CGI CGI(Common Gat

  • Python源码解析之List

    一.列表结构体 创建列表C语言底层的结构体 lists = [] list.append('name') list.append('age') list.append('grade') typedef struct{ struct _object *_ob_next; struct _object *_ob_prev; // python内部将对象放在链表进行内存管理 Py_ssize_t ob_refcnt; // 引用计数器,就是多少变量用了它 PyObject **ob_item; //

  • 源码解析python中randint函数的效率缺陷

    目录 一.前言 二.对randint()运行效率的测试 三.从源码分析randint()的缺陷 random.random() random.randint() 四.更快的生成随机整数的方法 random.random() 直接使用 getrandbits() 使用 Numpy.random 一.前言 前几天,在写一个与差分隐私相关的简单程序时,我发现了一些奇怪的东西:相对于其他的随机数生成函数,Python的random.randint()函数感觉很慢. 由于 randint() 是 Pyth

  • Android图片加载利器之Picasso源码解析

    看到了这里,相信大家对Picasso的使用已经比较熟悉了,本篇博客中将从基本的用法着手,逐步的深入了解其设计原理. Picasso的代码量在众多的开源框架中算得上非常少的一个了,一共只有35个class文件,但是麻雀虽小,五脏俱全.好了下面跟随我的脚步,出发了. 基本用法 Picasso.with(this).load(imageUrl).into(imageView); with(this)方法 public static Picasso with(Context context) { if

  • Android 中 SwipeLayout一个展示条目底层菜单的侧滑控件源码解析

    由于项目上的需要侧滑条目展示收藏按钮,记得之前代码家有写过一个厉害的开源控件 AndroidSwipeLayout 本来准备直接拿来使用,但是看过 issue 发现现在有不少使用者反应有不少的 bug ,而且代码家现在貌似也不进行维护了.故自己实现了一个所要效果的一个控件.因为只是实现我需要的效果,所以大家也能看到,代码里有不少地方我是写死的.希望对大家有些帮助.而且暂时也不需要 AndroidSwipeLayout 大而全的功能,算是变相给自己做的项目精简代码了. 完整示例代码请看:GitHu

  • .NET Core源码解析配置文件及依赖注入

    写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表.不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦. 这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原理.配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等. 俗话说,授人以鱼不如授人以渔,所以文章旨在带着大

随机推荐