نوشته شده به وسیلهی: Mohsen در 1 سال 6 ماه پیش تحت عنوان پایتون کتابخانه-های-پایتون rich
فهرست مطالب
مقدمه🔗
کتابخانهی rich
ابزار نیرومندی در زبان پایتون است برای نمایش ویژگیهای rich در ترمینال! مثلا با آن میتوان:
- متنها را رنگبندی کرد.
- متنها را bold، italic و یا زیر خط دار کرد.
- متنها را چپچین، راستچین و یا وسطچین کرد.
- متنها را به یک آدرس اینترنتی لینک کرد.
- جدول ساخت و اطلاعات را در جدول نمایش داد.
- source code ها را بسته به زبان برنامهنویسی به صورت syntax highlight شده نمایش داد.
- کدهای Markdown را به صورت تفسیر شده نمایش داد.
- صفحهی ترمینال را لایه بندی کرد و در هر لایه چیزی قرار داد.
- progress bar ساخت و وضعیت پیشرفت یک یا چند عملیات را به صورت بصری به کاربر نشان داد.
- tree ساخت و اطلاعات را به صورت شاخه شاخه و تو در تو نمایش داد.
rich
ابزار فوقالعاده نیرومندی است. بعد از آشنایی با آن، راز ترمینالهایِ زیبایی که معمولا در هنگام نصب پکیجهای نرمافزاری میبینید برایتان فاش خواهد شد و دیگر چندان با اعجاب به آنها نگاه نخواهید کرد، برای اینکه خود قادر به نوشتن چنین برنامههایی خواهید بود.
از حاشیهها کم کنم و به اصل مطلب بپردازم. در این پست، ابتدا چند کلاسِ اصلی این کتابخانه را معرفی و با تکه کدهای کوچک، عملکرد آنها را به صورت تکی بررسی میکنیم و سپس در ادامه پروژهای کوچک تعریف و با مطالب آموخته، شده آن را پیادهسازی خواهیم کرد.
قبل از هر چیز میبایست rich
را نصب کرد. نصب کتابخانهی rich
راحت و سر راست است و با دستور pip
به صورت زیر قابل انجام است:
$ pip install rich
اگر با pip
آشنایی ندارید و یا علاقمند به مرور دانستههای خود هستید، پست در مورد pip را بخوانید.
کلاس Prompt🔗
معرفی کلاسها را از کلاس Prompt
شروع میکنیم که با آن از کاربر تقاضا میکنیم که اطلاعی را وارد کند تا برنامه آن را بخواند. کد زیر نحوهی import و استفاده از این کلاس را نشان میدهد:
from rich.prompt import Prompt
name = Prompt.ask("Enter your name")
print("You entered: ", name)
میتوان به آن مقدار پیشفرض هم داد؛ یعنی اگر کاربر چیزی وارد نکند و مستقیما دگمهیEnter را بزند برنامه مقداری که ما تعیین کردهایم را بر میگرداند. حتی میتوان به آن لیستی از مقادیر را داد و کاربر را مجبور کرد که فقط، یکی از مقادیر مشخص شده را وارد کند. در این صورت rich
چرخهی دریافت دیتا را تا زمانی که مقدار صحیحی وارد نشده است تکرار میکند. مقدار پیش فرض به وسیلهی default keyword argument و لیست مقادیر قابل قبول توسط choices keyword argument مشخص میشود. کد زیر این موضوع را نشان میدهد:
from rich.prompt import Prompt
from rich.console import Console
name = Prompt.ask("Enter a valid name", default="Sara", choices=["Sara", "Mohsen", "Nima", "Ali"])
console = Console()
console.print(f"name: [red bold]{name}")
نمونه خروجی کد فوق را در تصویر زیر میبینید:
کلاس Confirm🔗
Confirm
در واقع یک نوع Prompt
خاص است و وظیفه دارد که کاربر را مجبور به وارد کردن y یا n کند. خروجی آن True یا False است.
from rich.prompt import Confirm
response = Confirm.ask("به نظر شما این پست مفید است؟")
print(response)
نمونه خروجی کد بالا به صورت زیر است:
کلاس Padding🔗
کلاس Padding
برای انداختن فضای خالی در اطراف متن و یا هر چیزی که توسط rich
قابل render شدن باشد استفاده میشود. n فضای خالی در بالا و پایین یک renderable به معنای n خط خالی است و n فضای خالی در سمت چپ و راست آن به معنای n کاراکتر Space است.
قبل از توضیح بیشتر کد زیر را ببینید:
from rich.console import Console
from rich.padding import Padding
console = Console()
widget = Padding("My name is Mohsen", 2)
console.print(widget)
خروجی کد بالا به صورت زیر است. به دو خط خالی بالای متن دقت کنید. اگر چه شاید واضح نباشد ولی دو کاراکتر Space هم در ابتدای خطی که «نوشته» است وجود دارد.
اگر فضای خالی اطراف renderable یکسان نباشد به جای آرگومان دوم میتوانیم از یک tuple استفاده کنیم. اگر tuple حاوی دو عدد باشد عدد اول به معنای تعداد خط خالی در بالا و پایین و عدد دوم به معنای تعداد کاراکتر Space در سمت چپ و راست renderable است. اگر tuple حاوی چهار عدد باشد به ترتیب نشان دهندهی تعداد خط خالی در بالا، تعداد کاراکتر Space در سمت راست، تعداد خط خالی در پایین و تعداد کاراکتر Space در سمت چپ renderable است. اگر با css آشنا باشید این شیوهی مقدار دهی برایتان آشناست.
کلاس Padding
یک keyword argument به نام expand دارد که مقدار True یا False میگیرد. به طور پیشفرض مقدار آن True است و به این معنی است که از کل عرضِ «در دسترس» استفاده خواهد کرد. میتوانید آن را False کنید تا فقط به اندازهی مورد نیاز فضا اشغال کند. کد زیر را ببینید:
from rich.console import Console
from rich.padding import Padding
console = Console()
widget = Padding("My name is Mohsen", (2, 1), expand=False)
console.print(widget)
کد فوق را اجرا میکنیم و خروجی را به برنامهی sed
میفرستیم تا تمام کاراکترهای Space را با _ پر کند. میبینید که در ابتدا و انتهای نوشته یک کاراکتر Space قرار دارد و کل فضای اختصاص داده شده به این widget به اندازهی مقدار متن است. نکتهی قابل توجه اینکه خطهای خالی فقط شامل کاراکتر \n
نیستند بلکه با کاراکترهای Space پر شده اند.
کلاس Table🔗
Table
یا جدول، ابزار فوقالعادهای برای نمایشِ قابل فهم اطلاعات است و چه خوب است که در ترمینال هم بتوانیم جدول بسازیم. سادهترین جدول دارای تعدادی ستون و تعدادی سطر است. بعد از import کردن کلاس Table
در برنامه و ساخت یک instance از آن، دو متد در اختیار ما قرار میگیرد، یکی به نام add_column
و دیگری به نام add_row
. با این دو متد و سپس print کردن آبجکت table در کنسول، جدول به نمایش درمیآید.
کد زیر اطلاعات اعضای یک خانواده را نمایش میدهد:
from rich.console import Console
from rich.table import Table
table = Table(title="Mr Alavi's Family",caption="This is a small family", title_style="bold red")
table.add_column("Name", style="blue")
table.add_column("Family", style="yellow")
table.add_column("Role")
table.add_column("Age")
table.add_row("Ali", "Alavi", "Father", "59")
table.add_row("Sara", "Saravi", "Mother", "53", style="bold red")
table.add_row("Mohammad", "Alavi", "Son", "35")
table.add_row("Jalal", "Alavi", "Son", "30")
table.add_row("Fateme", "Alavi", "Daughter", "28")
console = Console()
console.print(table)
خروجی کد فوق به صورت زیر است:
به دو خط highlight شده توجه کنید. در خط چهارم، در تابع constructor کلاس Table
میتوانیم keyword argumentهای زیادی را استفاده کنیم. اینجا از سه kwarg استفاده کردهام. title عنوان جدول را در بالای آن نمایش میدهد. title_style ویژگیهای نمایشی title را ست میکند. در اینجا گفتهام که title را bold و به رنگ قرمز کند. caption توضیحی اضافه است و در پایین جدول نشان داده میشود. بعد از ایجاد جدول باید آن را print کنیم. برای این منظور آبجتی از کلاس Console
میسازیم و سپس متد print آن را با آبجکت جدول صدا میزنیم. این کاری است که در دو خط آخر کردهایم.
یک بار دیگر تصویر فوق را نگاه کنید. فضایی که rich
به جدول اختصاص داده است دقیقا به اندازهی مورد نیاز جدول است. اگر میخواهید از تمام عرض در دسترس استفاده کنید expand=True
را در constructor کلاس Table
اضافه کنید.
کلاس Columns🔗
آیا تا به حال به خروجی دستور ls
در خط فرمان سیستم عاملهای شبه یونیکس توجه کردهاید که چگونه بسته به عرض ترمینال خروجی را در تعداد ستونهای متغیر، اما مرتب و منظم نشان میدهد؟ ایجاد این نوع خروجی در rich
به سادگی استفاده از کلاس Columns
است. این کلاس به عنوان آرگومان نخست یک iterable میگیرد. اگر از keyword arguments هایی که میتوانیم استفاده کنیم صرف نظر کنیم تنها کافیست که آبجکت حاصله را به متد print آبجکتی از کلاس Console
بدهید. سخت نیست! مثال زیر را ببینید:
from rich.console import Console
from rich.columns import Columns
from urllib.request import urlopen
import json
results = urlopen("https://randomuser.me/api?results=30&nat=us")
records = json.loads(results.read())["results"]
names = []
for record in records:
names.append("<" + record['name']['first'] + " " + record['name']['last'] + ">")
columns = Columns(names)
console = Console()
console.print(columns)
از سایت randomuser.me سی رکورد فیک میخوانیم و نام و نامخانوادگی آنها را استخراج و در یک لیست میگذاریم. برای اینکه حدود متن را به درستی متوجه شویم هر کدام را بین <>
محصور میکنیم تا ببینیم که آیا rich
ستونها را به درستی تنظیم کرده است یا نه. یک نمونه از خروجی برنامه را در زیر میبینید. خروجی برنامه برای شما فرق خواهد داشت، برای اینکه رکوردها هر بار به صورت رندوم و فیک تولید میشوند.
به نظر میرسد که rich
کارش را درست انجام داده است!
کلاس Tree🔗
Tree مفهوم پیچیدهای نیست اما شاید به نظر برسد که پیادهسازی آن سخت است ولی با rich
این گونه نیست. سختترین نمودارهای درختی و تو در تو را میتوانیم به راحتی با کلاس Tree
ایجاد کنیم. باز هم یادآور میشوم که در نهایت درخت ایجاد شده باید به متد print آبجکتی از کلاس Console
فرستاده شود تا در صفحه به نمایش درآید.
from rich.console import Console
from rich.tree import Tree
tree = Tree("/")
bin_ = tree.add("bin")
home = tree.add("home", guide_style="underline2")
bin_.add("cat", style="overline")
bin_.add("ls")
bin_.add("[italic cyan]awk")
home.add("mohsen", style="red bold").add("mohsen_documents")
home.add("ehsan").add("ehsan_documents")
tree.add("media")
tree.add("cdrom")
console = Console()
console.print(tree)
راز توانایی بالای rich
در ساختن درخت در متد add نهفته است. این متد شاخهی جدید میسازد و یک درخت جدید بر میگرداند، به صورتی که اگر متد add درخت برگشتی را صدا کنید شاخه جدید به عنوان فرزند آن ساخته میشود. به عنوان مثال در خط پنجم شاخهای به نام bin در درخت اصلی میسازیم و آبجکت برگشتی را در متغیری به نام _bin
ذخیره میکنیم. در خطوط هشت و نه و ده متد add این آبجکت را صدا میزنیم و شاخههایی که ایجاد میشوند به عنوان فرزندان شاخهی bin منظور میشوند.
نکتهی دیگر در مورد style keyword argument و guide_style keyword argument است. اگر شاخهای زیر شاخه داشته باشد استایل داده شده بر تمام زیر شاخهها هم اعمال میشود. این موارد را در شکل زیر به خوبی مشاهده میکنید.
کلاس Syntax🔗
شاید زمانی فکر کرده باشید که چه خوب میشد اگر میتوانستیم در ترمینال source codeها را به صورت syntax highlight شده ببینیم. من به این موضوع فکر کرده بودم و راستش زیاد پیگیرش نشده بودم تا اینکه در rich
با آن مواجه شدم و از سادگی و سر راستیاش یکه خوردم. کافیست آبجکتی از کلاس Syntax
بسازید و source code را به عنوان اولین آرگومان و نامِ زبانِ برنامه را عنوان دومین آرگومان به آن بدهید و آبجکت دریافتی را در console پرینت کنید.
بیایید برنامهای بنویسیم که نام و زبان یک فایل را از خط فرمان بگیرد و محتوایش را به صورت syntax highlight شده در ترمینال نمایش دهد.
from rich.console import Console
from rich.syntax import Syntax
from rich.padding import Padding
import sys
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} filename file-language", file=sys.stderr)
sys.exit(1)
console = Console()
try:
with open(sys.argv[1], "r") as file:
syntax = Syntax(file.read(), sys.argv[2], padding=(1, 0), line_numbers=True)
console.print(Padding(syntax, (1, 0)))
except Exception as e:
print(e)
به keyword argumentهای استفاده شده در constructor کلاس Syntax
توجه کنید. با استفاده از line_numbers=True
خطها را شمارهگذاری میکنیم و با padding=(1, 0)
یک خط خالی در بالا و پایین داخل کادر کد ایجاد میکنیم (در تصویر پایین این قسمت با رنگ پس زمینه مشخص است.) از آنجایی که به خاطر وجود شمارهی خطها سمت چپ دارای فضای کافی است در چپ و راست نیاز به فضای خالی نداریم لذا عدد مربوطه را صفر میگذاریم. موقع پرینت آبجکت با استفاده از کلاس Padding
یک خط خالی در بالا و پایین کادر کد ایجاد میکنیم تا ظاهر کار زیباتر و تمرکز بر کد highlight شده بیشتر شود.
حالا به عنوان مثال کد همین برنامه را syntax highlight میکنیم:
کلاس Markdown🔗
با rich
میتوان حتی کدهای Markdown را هم در ترمینال به صورت تفسیر شده نشان داد. کافیست از کلاس Markdown
یک آبجکت بسازید. کد «Markdown» را باید به عنوان اولین آرگومان به تابع constructor بفرستید و بعد آبجکت حاصله را در console پرینت کنید. همان طور که احتمالا تا الان متوجه شدهاید این شیوهایست که در مورد بسیاری از کلاسها به کار میرود. مثلا در مورد کلاسهای Syntax
و Tree
هم روند کار دقیقا به همین صورت بود.
برنامهی زیر نام یک فایل Markdown را از خط فرمان میخواند و آن را به صورت render شده در ترمینال نمایش میدهد.
from rich.console import Console
from rich.markdown import Markdown
import sys
console = Console()
try:
file = open(sys.argv[1], "r")
md = Markdown(file.read())
console.print(md)
except Exception as e:
print(e, file=sys.stderr)
اگر فایل Markdown برای تست ندارید این فایل را دانلود کنید. اگر برنامه را با این فایل فراخوانی کنیم خروجی به صورت زیر خواهد بود:
توجه کنید که علاوه بر تمام موارد نظیر رندر شدن دقیق لینکها، بلاکِ کد به صورت کامل syntax highlight شده است.
کلاس Status🔗
این ویدیو را ببینید تا متوجه شوید در مورد چه چیزی صحبت میکنیم.
خُب! پیداست که کاربرد آن کجاست. برای ساختن چنین چیزی باید از کلاس Status
استفاده کنیم. تابع constructor این کلاس یک متن یا یک renderable میگیرد و یک context manager برمیگرداند. اگر نمیدانید context manager چیست به طور خلاصه بگویم که کلاسی که پروتوکل context manager را پیادهسازی کرده باشد را میتوان در عبارت with
پایتون استفاده کرد1.
کد زیر را ببینید تا بعد در مورد آن بیشتر صحبت کنیم. ویدیوی بالا در واقع خروجی این کد است:
from rich.console import Console
import time
console = Console()
console.print("[bold red]Start doing jobs")
with console.status("Please waiting...") as status:
for i in range(5):
console.log(f"job number #{i}")
time.sleep(1)
console.print("[bold blue]Jobs done")
در خط highlight شده، console.status("Please waiting...")
یک آبجکت از کلاس Status
برمیگرداند. میتوانستیم این کار را نکنیم و مستقیما و به صورت زیر آبجکتی از کلاس Status
بسازیم:
# ...
from rich.status import Status
# ...
with Status("Please waiting...") as status:
# ...
spinner را میتوان توسط kwarg ای به همین نام تغییر داد.
کلاس Progress🔗
یک شیوهی ساخت progress bar استفاده از تابع track
است. ابتدا باید این تابع را import کرد و سپس آن را با یک iterable فراخواند. track
هر بار یک عنصر از این iterable را بر میگرداند و از آنجایی که تعداد عناصر را با استفاده از تابع len
محاسبه کرده دقیقا میداند که چند عنصر را برگردانده و چند عنصر باقی مانده است و به این ترتیب میتواند یک progress bar بسازد.
from rich.progress import track
from time import sleep
for i in track(range(100), description="Processing data"):
sleep(0.05)
برای دیدن خروجی برنامه باید از آن ویدئو ضبط کرد ولی در این جا به عکسی کفایت کنید!
اما گاهی اوقات لازم است که چندین progress bar داشته باشیم. مثلا یکی وضعیتِ پیشرفتِ دانلود را اطلاع دهد و دیگری وضعیت پردازش اطلاعات دانلود شده را. برای این منظور لازم است مستقیما از کلاس Progress
استفاده کنیم.
هر progress bar را به عنوان یک task میشناسیم و به تعداد دلخواه میتوانیم task اضافه کنیم. کلاس Progress
به صورت context manager 1 طراحی شده است لذا میتوانیم آن را در عبارت with پایتون استفاده کنیم.
from rich.progress import Progress
import time
with Progress(transient=True) as progress:
task1 = progress.add_task("Downloading...", total=100)
task2 = progress.add_task("Processing...", total=100)
task3 = progress.add_task("Generating reports...", total=100)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
progress.update(task3, advance=0.1)
time.sleep(0.005)
با متد add_task
به progress bar تَسک اضافه میکنیم. نام عملیات را به عنوان اولین آرگومان و سپس تعداد مراحل این task را در قالب total keyword argument مشخص و به متد ارسال میکنیم. خروجی این متد task ID است که عددی integer است. سپس در یک حلقه بررسی میکنیم که آیا progress تمام شده است یا نه. درستی این شرط منوط به این است که تمام وظیفهها یا task ها تمام شده باشند، در غیر این صورت حلقه یک بار دیگر تکرار میشود و task را آپدیت میکنیم. این کار به وسیلهی متد update
انجام میشود. گفتیم که هر task دارای یک task ID است. متد update این taskID را برای شناسایی این که کدام task را باید آپدیت کند میگیرد. مقدار پیشرفت هر task توسط advance keyword argument تعیین میشود.
تصویر اجرای برنامه در لحظهای مشخص به صورت زیر است:
transient که در کد بالا آن را در کانستراکتور Progress
استفاده کردهایم باعث میشود که پس از این که تمام task ها تمام شدند کل فضای اختصاص داده شده به progress bar ها از روی صفحه پاک شود. انگار که هیچ وقت progress bar ای آنجا نبوده است. این مورد را احتمالا هنگام استفاده از دستورهای man
یا more
یا less
در خط فرمان سیستمهای شبه یونیکس دیدهاید که چگونه بعد از خاتمه دادن به برنامه تمام اطلاعات این برنامهها از روی ترمینال حذف میشود.
کلاس Panel🔗
از کلاس Panel
برای انداختن border دور متن یا هر renderable دیگری میتوان استفاده کرد. تابع constructor این کلاس دو keyword argument مهم به نامهای title و subtitle دارد که به ترتیب برای ست کردن «عنوان» در بالای پنل و «زیر عنوان» در پایین پنل استفاده میشوند. پنلها به صورت پیشفرض از تمام عرض دسترس استفاده میکنند. اگر چنین رفتاری مطلوب شما نبود در تابع کانستراکتور expand=False
را نیز وارد کنید.
from rich.panel import Panel
from rich.console import Console, Group
panel1 = Panel(
"[bold blue]expand=True[/]\n\n[red]This is default configuration of the Panel Class",
title="Panel title",
subtitle="Panel Subtitle",
title_align='right',
subtitle_align='left'
)
panel2 = Panel("[italic underline]expand=False[/]\n\ntitle and subtitle kwargs are not set", expand=False)
group = Group(panel1, panel2)
console = Console()
console.print(group)
در مثال فوق دو پنل تعریف کردهایم. اولین پنل به صورت پیشفرض expand=True
است و از تمام عرضی که در دسترسش است استفاده میکند. علاوه بر این title و subtitle را برای آن ست میکنیم و صراحتا با kwarg های title_align و subtitle_align به آن میگوییم که title را در سمت راست و subtitle را در سمت چپ قرار دهد.
در پنل دوم در تابع کانستراکتور کلاس پنل expand را مساوی False قرار میدهیم و title و subtitle را هم سٍت نمیکنیم. همان طور که در تصویر زیر میبینید فضایی برای عنوان و زیر عنوان در نظر گرفته نمیشود و renderable دارای یک border یکپارچه و کامل است.
نکته حايز اهمیت دیگر این که میخواهیم دو آبجکتِ پنل را همزمان با هم در صفحه پرینت کنیم. به این منظور کلاس Group
را import کردیم و از آن یک آبجکت ساختیم. renderable ها را به ترتیب مورد نظرخودمان به تابع کانستراکتور کلاس Group
ارسال و نهایتا آبجکت حاصله از کلاس Group
را در console پرینت کردیم.
خروجی کد بالا به صورت زیر است:
کلاس Layout🔗
کلاس Layout
برای لایه بندی فضای ترمینال استفاده میشود. فرض کنید باید ترمینال را به دو بخش چپ و راست تقسیم کنیم و بخش چپ سه چهارم عرض ترمینال را بگیرد و بخش سمت راست یک چهارم باقیمانده را. بعد فرض کنید فضای سمت چپ را باید به دو قسمت بالا و پایین طوری تقسیم کنیم که هر دو بخش دارای ارتفاع برابر باشند. حالا ترمینال به سه بخش تقسیم شده است. الان میتوانیم در هر بخش یک renderable قرار دهیم و نهایتا آبجکت layout اصلی را در console پرینت کنیم.
مثال بالا احتمالا یکی از بدترین انواع لایه بندی صفحه باشد ولی بیایید آن را پیادهسازی کنیم:
from rich.console import Console
from rich.panel import Panel
from rich.layout import Layout
from rich.align import Align
main_layout = Layout()
main_layout.split_row(
Layout(name="left", ratio=3),
Layout(name="right", ratio=1)
)
main_layout["left"].split_column(
Layout(name="left_top"),
Layout(name="left_bottom")
)
left_top = Panel(Align("This is left-top panel", align='left', vertical='middle'))
left_bottom = Panel(Align("This is left-bottom panel", align='right', vertical='top'))
right = Panel(Align("This is right panel", align='center', vertical='bottom'))
main_layout['left_top'].update(left_top)
main_layout['left_bottom'].update(left_bottom)
main_layout['right'].update(right)
console = Console()
console.print(main_layout)
خروجی کد بالا به صورت زیر است:
با استفاده از کلاس Layout
لایهی اصلی را میسازیم و سپس با استفاده از آبجکت به دست آمده و متدهای split_row
که برای تقسیم سطر و ایجاد ستون و متد split_column
که برای تقسیم ستون و ایجاد سطر استفاده میشود میتوانیم لایههای دیگر بسازیم. ورودی هر دو متد ابتدائا آبجکتهای لایههای جدید و سپس kwargها هستند. ratio kwarg نسبت فضای لایههای فرزند به هم را مشخص میکند. مثلا در بالا گفتهایم که سه واحد فضا را به لایهی left اختصاص بده و یک واحد فضا را به لایه right (به صورت ضمنی گفتهایم که چهار واحد فضا داریم.) لایه های جدید باید name keyword argument شان سِت شده باشد. این name بعدا به عنوان اندیس در آبجکت لایهی اصلی استفاده میشود و میتوانیم خیلی سریع به هر لایه دسترسی داشته باشیم:
# ...
main_layout.split_row(
Layout(name="left", ratio=3),
Layout(name="right", ratio=1)
)
main_layout["left"].split_column(
Layout(name="left_top"),
Layout(name="left_bottom")
)
# ...
در کد بالا renderable ها (که در این جا همان متنهای ما هستند) را به کلاس Align
ارسال کردهایم تا آنها را آماده کنیم که در فضایی که نهایتا قرار میگیرند چپ-چین، راست-چین، وسط-چین و یا اینکه در بالا یا وسط یا پایین فضا قرار بگیرند. خروجی کلاس Align
همچنان یک renderable است پس بدون دردسر میتوانیم آن را به کلاس Panel
ارسال کنیم تا دور آن یک border بیندازد:
# ...
left_top = Panel(Align("This is left-top panel", align='left', vertical='middle'))
left_bottom = Panel(Align("This is left-bottom panel", align='right', vertical='top'))
right = Panel(Align("This is right panel", align='center', vertical='bottom'))
# ...
حالا سه پنل داریم که باید در داخل لایهها قرار گیرند. در بالا گفتیم که هر لایهی فرعی در هنگام ساخته شدن باید یک name keyword argument داشته باشد. با این name و استفاده از آن به عنوان اندیس آبجکت لایهی اصلی میتوانیم هر لایه را پیدا کنیم. بعد از یافتن هر لایه، با فراخوانی متد update
و ارسال پنل نظیر به عنوان آرگومان به آن، آن را آپدیت میکنیم و renderable جدید را درون آن لایه میاندازیم:
# ...
main_layout['left_top'].update(left_top)
main_layout['left_bottom'].update(left_bottom)
main_layout['right'].update(right)
# ...
خُب. باقی کار ساده است. باید آبجکت لایهی اصلی را در console پرینت کنیم؛ کاری که بارها آن را در مورد آبجکت کلاسهای دیگر نیز انجام دادهایم.
کلاس Live🔗
با Live display بیگانه نیستید. آن را در Progress bar ها و status ها به کار بردهایم. با استفاده از مفهوم Live display و کلاس Live
میتوان یک renderable را بر روی ترمینال نمایش داد و سپس آن را در بازههای زمانی معین refresh کرد. به این صورت اطلاعات مداوما بر روی صفحه ترسیم میشود و کاربر بدون اینکه متوجه تغییرات وسیع انجام شده در پسزمینه شود نمایشی یکنواخت و بدون قطعی از اطلاعات در صفحه میبیند.
کلاس Live
را باید با عبارت with
و در قالب context manager استفاده کرد. وقتی کنترل از context manager خارج شود عملکرد Live display مرتبط با آن renderable خاص هم تمام میشود.
from rich.live import Live
from rich.table import Table
from rich.console import Console
from time import sleep
table = Table("Name", "Family", "City")
info = [
{'Name': 'Mohsen', 'Family': 'Safari', 'City': 'Tehran'},
{'Name': 'Sara', 'Family': 'Karimi', 'City': 'Isfahan'},
{'Name': 'Abbas', 'Family': 'Rajabi', 'City': 'Tafresh'},
{'Name': 'Taghi', 'Family': 'Afshar', 'City': 'Ghaemshahr'},
]
with Live(table):
for i in info:
table.add_row(i['Name'], i['Family'], i['City'])
sleep(1)
در کد بالا ابتدا آبجکتی از کلاس Table
تعریف میکنیم و سپس آن را به کلاس Live
ارسال میکنیم. Live
این renderable را به صورت پی در پی بر روی صفحه ترسیم میکنیم. حالا اگر در میانهی این رسم شدنها اطلاعات را تغییر دهیم کاربر تغییرات را خیلی یکنواخت و روان بر روی صفحه میبیند.
ویدیوی زیر اجرای برنامه را نشان میدهد:
یک پروژهی کوچک🔗
میخواهیم برنامهای بنویسیم که از سایت randomuser.me اطلاعات چند کاربر فیک را بخواند و در سه progress bar روند پیشرفت دانلود و پیشرفت پردازش اطلاعات و پیشرفت درج اطلاعات در جدول را نمایش دهد. در این برنامه تقریبا از تمام کلاسهای فوق استفاده میکنیم. تصویر زیر لحظهای از زمان اجرای برنامه را نشان میدهد.
ویدیوی اجرای برنامه را نیز در همین سایت و از اینجا ببینید.
و اما کد برنامه:
from rich.console import Console
from rich.layout import Layout
from rich.table import Table
from rich.progress import Progress
from rich.panel import Panel
from rich.prompt import IntPrompt
from rich.text import Text
from rich.padding import Padding
from rich.align import Align
from rich.live import Live
from time import sleep
from urllib.request import urlopen
import sys
import json
def read_data():
obj = urlopen("https://randomuser.me/api/?results=1")
return json.loads(obj.read())["results"][0]
console = Console()
console.set_window_title("DOKAJ.IR")
number_of_records = IntPrompt.ask("How many records you want to read?", default=10)
if number_of_records <= 0:
sys.exit(0)
layout = Layout()
layout.split_column(
Layout(name="top"),
Layout(name="footer", size=3)
)
layout["top"].split_column(
Layout(name="header", size=3),
Layout(name="body")
)
layout["body"].split_row(
Layout(name="left"),
Layout(name="right")
)
layout["left"].split_column(
Layout(name="progress", size=5),
Layout(name="status"),
)
layout["header"].update(
Panel(
Align(
"[bold]Playing with [link=https://rich.readthedocs.io/en/stable/index.html]\
Python's Rich module", align="center"
)
)
)
layout["footer"].update(
Panel("Visit [black on white bold link=https://dokaj.ir]DOKAJ.IR[/] for more info.")
)
progress = Progress()
task_readding = progress.add_task("Read data...", total=number_of_records, start=False)
task_processing = progress.add_task("Processing data...", total=number_of_records, start=False)
task_updating = progress.add_task("Updating table...", total=number_of_records, start=False)
table = Table(
"Name", "Family", "Country", "Age",
expand=True,
header_style="bold",
title="Students list",
title_style="Bold cyan",
caption="This is my students list",
caption_style="bold italic"
)
status = Text("", overflow="fold")
layout["progress"].update(Panel(progress, title="Progress"))
layout["status"].update(Panel(status, title="Status"))
layout["right"].update(Panel(table, title="Information"))
data = []
with Live(layout, refresh_per_second=4, vertical_overflow="visible") as live:
progress.start_task(task_readding)
while not progress.tasks[task_readding].finished:
try:
result = read_data()
data.append([
result["name"]["first"],
result["name"]["last"],
result["location"]["country"],
str(result["dob"]["age"])
])
progress.update(task_readding, advance=1)
status.append("New data fetched: ")\
.append(f"{result['name']['first']} {result['name']['last']}\n",
style="bold italic cyan")
except Exception as e:
status.append(f"{e}\n", style="red")
sleep(1)
progress.start_task(task_processing)
while not progress.tasks[task_processing].finished:
progress.update(task_processing, advance=1)
sleep(0.5)
data.reverse()
progress.start_task(task_updating)
while not progress.tasks[task_updating].finished:
info = data.pop()
table.add_row(*info)
progress.update(task_updating, advance=1)
status.append("Table updated with: ") \
.append(f"{info[0]} {info[1]}\n", style="bold italic blue")
sleep(0.5)
کد فوق را به صورت زیر اجرا کنید:
$ python dokaj_python_rich_program.py
پایان مطلب و نتیجهگیری🔗
در این راهنما نگاهی خیلی ساده به rich
داشتیم ولی با همین سادگی کارهای بزرگی انجام دادیم که قبلا تصور انجام دادنش را هم نمیکردیم. اینجا نقطهی پایان rich
نیست. حجم بالایی از اطلاعات وجود دارد که در این راهنما پوشش داده نشده است. این سایت منبع بسیار خوبی برای تکمیل اطلاعات شماست. در python shell نیز میتوانید در مورد کلاسها و متدها بخوانید. مثلا در زیر در مورد کلاس Console
کسب اطلاع میکنیم. چون حجم خروجی دستور بالاست فقط چند خط ابتدایی را قرار میدهم:
>>> from rich.console import Console
>>> help(Console)
Help on class Console in module rich.console:
class Console(builtins.object)
# ....
شاد باشید.
من محسن هستم؛ برنامهنویس سابق PHP و Laravel و Zend Framework و پایتون و فلسک. تمرکزم بیشتر روی لاراول بود! الان از صفر مشغول مطالعات اقتصادی هستم.
برای ارتباط با من یا در همین سایت کامنت بگذارید و یا به dokaj.ir(at)gmail.com ایمیل بزنید.
در مورد این مطلب یادداشتی بنویسید.