您好,登錄后才能下訂單哦!
小編給大家分享一下關(guān)于Python開源項目Rich源碼的案例分析,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
這篇文章對優(yōu)秀的開源項目Rich
的源碼進行解析,OMG,盤他。為什么建議閱讀源碼,有兩個原因,第一,單純學(xué)語言很難在實踐中靈活應(yīng)用,通過閱讀源碼可以看到每個知識點的運用場景,印象會更深,以后寫代碼的時候就能應(yīng)用起來;第二,通過閱讀優(yōu)秀的開源代碼,可以學(xué)習比人的代碼規(guī)范、設(shè)計思路;第三,參與到開源社區(qū),獲得更廣闊的的發(fā)展前景;第四,面試加分項。所以,有時間的話還是建議大家多讀讀優(yōu)秀開源項目的源碼。
下面進入今天的主題,這個開源項目的名字叫Rich
,地址:https://github.com/willmcgugan/rich 。這個項目是個英國老鐵開發(fā)的,比較友好的是有中文文檔。它的作用是可以在控制臺輸出富文本和精美的可視化格式(如:表格、進度條和markdown)。截圖感受一下
各種格式
進度條
效果看起來很酷炫,我忍不住看了一些代碼,發(fā)現(xiàn)作者用的是Python
3.8版本實現(xiàn)的,好多新特性我也不了解,所以在看源碼過程中還補了一下語法基礎(chǔ)。下面以一個例子來簡單看看Rich
的源碼,源碼的講解我盡量言簡意賅,重點講解源碼中涉及的一些關(guān)鍵的知識點。
先撿個軟柿子捏,如下:
from rich import print print('Hello, [bold yellow]World[/bold yellow]!')
輸出效果:
可以看到對單詞World
顯示為粗體、紅顏色。
先通過一張圖來看看大致流程
簡單來說就是將文本的格式轉(zhuǎn)化成標準輸出能夠識別的格式,然后輸出即可。下面來講解源碼,當我們調(diào)用print
函數(shù)時,最終程序會跳轉(zhuǎn)到console.py
文件的print
函數(shù)中,執(zhí)行以下代碼
調(diào)用self._collect_renderables
函數(shù)處理輸入的字符串,將需要格式化的部分標出來,返回的renderables
變量是一個Text
列表,因為輸入只有1個字符串,所以列表的大小為1,變量結(jié)果如下
Span(7, 12, 'bold red')
便是框出來需要格式化的內(nèi)容。
上述代碼還有一個with self
,它的作用我們一會兒再說。接著print
函數(shù)往下看
這里會遍歷剛剛提到的renderables
變量,先調(diào)用render
函數(shù)渲染輸入的文本,然后調(diào)用extend
函數(shù)將render
返回的結(jié)果添加到self._buffer
列表里。這里有幾個知識點簡單說一下
self._buffer
是函數(shù)調(diào)用,由于它加了@property
注解,所以調(diào)用是可以不用加小括號,它返回的是self._thread_locals.buffer
變量,該變量是List[Segment]
類型的self._thread_locals.buffer
變量用到dataclasses
模塊的field
函數(shù)初始化,初始化代碼為buffer: List[Segment] = field(default_factory=list)
,dataclasses
是Python
3.7 版本的新引入的模塊,field
函數(shù)可提供更加靈活的初始化方式,并且該模塊中的@dataclass
注解可以為類自動添加__init__
等方法,比較方便extend = self._buffer.extend
這種寫法將list
的extent
函數(shù)存到了臨時變量里,后續(xù)直接通過extend
調(diào)用該函數(shù),比對象名.extend
的方式更簡潔。下面我們來看render(renderable, render_options)
函數(shù)的渲染邏輯,該函數(shù)里會調(diào)用下面的代碼
render_iterable = renderable.__rich_console__(self, options)
在函數(shù)聲明里renderable
對象是RenderableType
類型的,但實際上Text
類型的,并且這兩種類型沒有繼承關(guān)系,這里沒太想明白作者為什么這樣搞。所以,這里的__rich_console__
函數(shù)我們要到text.py
文件中去找。__rich_console__
函數(shù)最終會調(diào)用Text
對象的render
函數(shù),核心代碼如下:
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())
調(diào)用get_style
函數(shù),將格式轉(zhuǎn)為Style
對象,如:'bold red'轉(zhuǎn)成Style
對象,然后按照不同的顯示格式進行‘分片',每個‘片段'構(gòu)造一個Segment
對象存儲文本及其對應(yīng)的格式。
get_style
函數(shù)會調(diào)用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
參數(shù)style_definition
取值為bold red
,分割后生成['bold', 'red']列表,當word
變量等于'bold'時,會執(zhí)行attributes[style_attributes[word]] = True
語句,執(zhí)行后attributes
等于{'bold': true}
,它是一個字典。當word
變量等于red
時,執(zhí)行color=word
語句。最終調(diào)用導(dǎo)數(shù)第二行構(gòu)造Style
對象,Style
對象最核心的兩個數(shù)據(jù)形式_attributes
和_color
, 前者是int
類型,在我們例子中取值是1,代表'bold',即:粗體。后者代表顏色,即:'red',它是Color
類型的,該類中有個屬性number
也是我們后續(xù)要用到的。
下面來看下__rich_console__
函數(shù)返回了哪些Segment
對象
可以看到有4個,每一個都有文本及其Style
對象。
回到render(renderable, render_options)
函數(shù),剛剛介紹了__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
方式返回。該關(guān)鍵字用來返回一個迭代器,也可以理解為一個列表。并且yield
返回有個特點,函數(shù)返回值只有真正被使用的時候才會執(zhí)行調(diào)用函數(shù)。
這樣,render(renderable, render_options)
函數(shù)就講解完了,返回上一層extend(render(renderable, render_options))
,通過extend
函數(shù)將4個Segment
對象保存到buffer
中,結(jié)果如下
然后print
方法就執(zhí)行完了??雌饋硪呀?jīng)結(jié)束了,然而控制臺打印的代碼貌似沒有看到。答案就在剛剛的with self
中,with
關(guān)鍵字使得執(zhí)行完代碼體后,會自動調(diào)用self
的__exit__
函數(shù)。__exit__
函數(shù)中調(diào)用_render_buffer
函數(shù)進行最終的輸出,核心代碼如下
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
函數(shù)是為了適應(yīng)控制臺的寬度,暫時忽略它。line
變量仍然是剛剛提到的4個Segment
對象,通過for text, style, is_control in line
直接將每個Segment
對象的屬性解出來并賦給text, style, is_control
變量,最終每個style
對象都會調(diào)用render
方法完成最后的渲染。
render
方法核心代碼如下
attrs = self._make_ansi_codes(color_system) rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
_make_ansi_codes
函數(shù)就不展開了, 其實就是利用上面提到的_attributes
和number
屬性生成標準輸出的能夠識別的格式,返回值attrs
的結(jié)果為1;31
,1取自_attributes
代表粗體,31中的1取自number
代表顏色,其他顏色取值是不同的,比如黃色是33,紫色是35。最后通過f-string
格式(新特性)生成rendered
變量,取值為[1;31mWorld[0m
它就是標準輸出流能夠識別的格式。
回到_render_buffer
函數(shù)中,調(diào)用rendered = "".join(output)
將4個渲染后的片段拼在一起,返回。返回后執(zhí)行的代碼如下:
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)
,至此整個流程就講解完了。如果你理解了上述邏輯,應(yīng)該可以通過下面代碼輸出同樣的效果
sys.stdout.write('Hello, \033[1;31mWorld\033[0m!')
所以Rich
做的就是把文字格式準成標準輸出流能識別的格式。
Rich
里用到的代碼確實挺新的,能學(xué)到很多東西,比直接看書來的快,有興趣的朋友可以自行閱讀。
源碼:https://github.com/willmcgugan/rich
看完了這篇文章,相信你對關(guān)于Python開源項目Rich源碼的案例分析有了一定的了解,想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。