منابع ترجمه · The Python Tutorial · فصل ۱۱

گشتی کوتاه در کتابخانهٔ استاندارد — بخش دوم (آموزش رسمی پایتون، فصل ۱۱)

۱۰ دقیقه مطالعه · به‌روزرسانی ۳ آبان ۱۴۰۵

منبع اصلی: The Python Tutorial, Chapter 11: Brief Tour of the Standard Library — Part II — Python Software Foundation

💡 این صفحه مثل یک نوت‌بوک Jupyter است: متغیر و تابعی که در یک سلول تعریف شود، در سلول‌های بعدی همین صفحه در دسترس است. سلول‌ها را به ترتیب اجرا کنید، یا با «⏮ تا اینجا» همهٔ سلول‌های قبلیِ اجرانشده را یک‌جا اجرا کنید.

این گشت دوم ماژول‌های پیشرفته‌تری را پوشش می‌دهد که از نیازهای برنامه‌نویسی حرفه‌ای پشتیبانی می‌کنند. این ماژول‌ها به‌ندرت در اسکریپت‌های کوچک دیده می‌شوند.

۱۱٫۱. قالب‌بندی خروجی

ماژول reprlib نسخه‌ای از repr() را فراهم می‌کند که برای نمایش‌های خلاصه‌شدهٔ ظرف‌های بزرگ یا عمیقاً تودرتو سفارشی شده است:

>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"

ماژول pprint کنترل پیچیده‌تری بر چاپ اشیای درون‌ساخت و تعریف‌شده توسط کاربر به شکلی که توسط مفسر قابل‌خواندن باشد ارائه می‌دهد. وقتی نتیجه از یک خط بلندتر است، «چاپگر زیبا» (pretty printer) شکست خط و تورفتگی اضافه می‌کند تا ساختار داده را واضح‌تر نشان دهد:

>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
...     'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
   'white',
   ['green', 'red']],
  [['magenta', 'yellow'],
   'blue']]]

ماژول textwrap پاراگراف‌های متن را طوری قالب‌بندی می‌کند که با یک عرض صفحهٔ مشخص جا شوند:

>>> import textwrap
>>> doc = """The wrap() method is just like fill() except that it returns
... a list of strings instead of one big string with newlines to separate
... the wrapped lines."""
...
>>> print(textwrap.fill(doc, width=40))
The wrap() method is just like fill()
except that it returns a list of strings
instead of one big string with newlines
to separate the wrapped lines.

ماژول locale به یک پایگاه داده از قالب‌های داده مختص فرهنگ دسترسی دارد. ویژگی grouping تابع قالب‌بندی locale راهی مستقیم برای قالب‌بندی اعداد با جداکنندهٔ گروه فراهم می‌کند:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv()          # get a mapping of conventions
>>> x = 1234567.8
>>> locale.format_string("%d", x, grouping=True)
'1,234,567'
>>> locale.format_string("%s%.*f", (conv['currency_symbol'],
...                      conv['frac_digits'], x), grouping=True)
'$1,234,567.80'

۱۱٫۲. قالب‌سازی (Templating)

ماژول string شامل کلاس همه‌کارهٔ Template با نحوی ساده‌شده و مناسب برای ویرایش توسط کاربران نهایی است. این به کاربران اجازه می‌دهد برنامه‌هایشان را بدون نیاز به تغییر برنامه سفارشی کنند.

قالب از نام‌های جانگهدار استفاده می‌کند که با $ و شناسه‌های معتبر پایتون (نویسه‌های الفبایی-عددی و زیرخط) ساخته می‌شوند. احاطه کردن جانگهدار با آکولاد به آن اجازه می‌دهد بدون فاصلهٔ میانی با حروف الفبایی-عددی بیشتر دنبال شود. نوشتن $$ یک $ تنهای escape‌شده می‌سازد:

>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'

متد substitute() وقتی یک جانگهدار در دیکشنری یا آرگومان کلیدواژه‌ای ارائه نشده باشد KeyError پرتاب می‌کند. برای برنامه‌های سبک ادغام نامه (mail-merge)، دادهٔ ارائه‌شدهٔ کاربر ممکن است ناقص باشد و متد safe_substitute() ممکن است مناسب‌تر باشد — اگر داده‌ای کم باشد جانگهدارها را بدون تغییر می‌گذارد:

>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
  ...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'

زیرکلاس‌های Template می‌توانند یک جداکنندهٔ سفارشی مشخص کنند. برای مثال، ابزار تغییر نام دسته‌ای برای یک مرورگر عکس ممکن است انتخاب کند از علامت‌های درصد برای جانگهدارهایی مانند تاریخ جاری، شمارهٔ دنبالهٔ تصویر یا قالب فایل استفاده کند:

>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
...
>>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format):  ')
Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f

>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
...     base, ext = os.path.splitext(filename)
...     newname = t.substitute(d=date, n=i, f=ext)
...     print('{0} --> {1}'.format(filename, newname))

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

کاربرد دیگر قالب‌سازی جدا کردن منطق برنامه از جزئیات قالب‌های خروجی متعدد است. این امکان جایگزین کردن قالب‌های سفارشی برای فایل‌های XML، گزارش‌های متنی ساده و گزارش‌های وب HTML را فراهم می‌کند.

۱۱٫۳. کار با چیدمان رکوردهای دادهٔ دودویی

ماژول struct توابع pack() و unpack() را برای کار با قالب‌های رکورد دودویی با طول متغیر فراهم می‌کند. مثال زیر نشان می‌دهد چگونه می‌توان بدون استفاده از ماژول zipfile روی اطلاعات سرایند در یک فایل ZIP حلقه زد. کدهای بسته‌بندی "H" و "I" به ترتیب اعداد بدون علامت دو بایتی و چهار بایتی را نشان می‌دهند. "<" نشان می‌دهد که آن‌ها اندازهٔ استاندارد دارند و در ترتیب بایت little-endian هستند:

import struct

with open('myfile.zip', 'rb') as f:
    data = f.read()

start = 0
for i in range(3):                      # show the first 3 file headers
    start += 14
    fields = struct.unpack('<IIIHH', data[start:start+16])
    crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

    start += 16
    filename = data[start:start+filenamesize]
    start += filenamesize
    extra = data[start:start+extra_size]
    print(filename, hex(crc32), comp_size, uncomp_size)

    start += extra_size + comp_size     # skip to the next header

۱۱٫۴. چندنخی (Multi-threading)

نخ‌دهی (threading) تکنیکی برای جدا کردن وظایفی است که به‌طور ترتیبی به هم وابسته نیستند. از نخ‌ها می‌توان برای بهبود پاسخ‌گویی برنامه‌هایی که ورودی کاربر را می‌پذیرند در حالی که وظایف دیگر در پس‌زمینه اجرا می‌شوند استفاده کرد. یک مورد استفادهٔ مرتبط اجرای I/O به‌طور موازی با محاسبات در نخ دیگر است.

کد زیر نشان می‌دهد ماژول سطح‌بالای threading چگونه می‌تواند وظایف را در پس‌زمینه اجرا کند در حالی که برنامهٔ اصلی به اجرا ادامه می‌دهد:

import threading, zipfile

class AsyncZip(threading.Thread):
    def __init__(self, infile, outfile):
        super().__init__()
        self.infile = infile
        self.outfile = outfile

    def run(self):
        with zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) as f:
            f.write(self.infile)
        print('Finished background zip of:', self.infile)

background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print('The main program continues to run in foreground.')

background.join()    # Wait for the background task to finish
print('Main program waited until background was done.')

چالش اصلی برنامه‌های چندنخی هماهنگ کردن نخ‌هایی است که داده یا منابع دیگر را به اشتراک می‌گذارند. برای این منظور، ماژول threading تعدادی اولیهٔ همگام‌سازی از جمله قفل‌ها (locks)، رویدادها (events)، متغیرهای شرطی (condition variables) و سمافورها (semaphores) فراهم می‌کند.

درحالی‌که این ابزارها قدرتمندند، خطاهای طراحی جزئی می‌توانند به مشکلاتی منجر شوند که بازتولیدشان دشوار است. بنابراین، رویکرد ترجیحی برای هماهنگی وظایف این است که همهٔ دسترسی به یک منبع را در یک نخ واحد متمرکز کنید و سپس از ماژول queue برای تغذیهٔ آن نخ با درخواست‌ها از نخ‌های دیگر استفاده کنید. برنامه‌هایی که از اشیای Queue برای ارتباط و هماهنگی بین نخ‌ها استفاده می‌کنند طراحی‌شان آسان‌تر، خواناتر و قابل‌اعتمادتر است.

۱۱٫۵. ثبت رویداد (Logging)

ماژول logging یک سیستم ثبت رویداد کامل‌ویژگی و انعطاف‌پذیر ارائه می‌دهد. در ساده‌ترین حالت، پیام‌های log به یک فایل یا به sys.stderr فرستاده می‌شوند:

import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')

این خروجی زیر را تولید می‌کند:

WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down

به‌طور پیش‌فرض، پیام‌های اطلاعاتی و اشکال‌زدایی سرکوب می‌شوند و خروجی به خطای استاندارد فرستاده می‌شود. گزینه‌های خروجی دیگر شامل هدایت پیام‌ها از طریق ایمیل، دیتاگرام‌ها، سوکت‌ها یا به یک سرور HTTP است. فیلترهای جدید می‌توانند مسیردهی متفاوت را بر اساس اولویت پیام انتخاب کنند: DEBUG، INFO، WARNING، ERROR و CRITICAL.

سیستم logging را می‌توان مستقیماً از پایتون پیکربندی کرد یا می‌توان آن را از یک فایل پیکربندی قابل‌ویرایش توسط کاربر بارگذاری کرد تا ثبت رویداد سفارشی بدون تغییر برنامه ممکن شود.

۱۱٫۶. ارجاع‌های ضعیف (Weak References)

پایتون مدیریت حافظهٔ خودکار انجام می‌دهد (شمارش ارجاع برای بیشتر اشیا و زباله‌روبی (garbage collection) برای حذف چرخه‌ها). حافظه اندکی پس از حذف آخرین ارجاع به آن آزاد می‌شود.

این رویکرد برای بیشتر برنامه‌ها خوب کار می‌کند اما گاهی نیاز هست اشیا را فقط تا زمانی که توسط چیز دیگری استفاده می‌شوند ردیابی کنیم. متأسفانه، صرفاً ردیابی آن‌ها یک ارجاع می‌سازد که آن‌ها را دائمی می‌کند. ماژول weakref ابزارهایی برای ردیابی اشیا بدون ساختن ارجاع فراهم می‌کند. وقتی دیگر به شیء نیاز نیست، به‌طور خودکار از جدول weakref حذف می‌شود و برای اشیای weakref یک callback فعال می‌شود. کاربردهای معمول شامل ذخیرهٔ موقت (caching) اشیایی است که ساختنشان پرهزینه است:

>>> import weakref, gc
>>> class A:
...     def __init__(self, value):
...         self.value = value
...     def __repr__(self):
...         return str(self.value)
...
>>> a = A(10)                   # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a            # does not create a reference
>>> d['primary']                # fetch the object if it is still alive
10
>>> del a                       # remove the one reference
>>> gc.collect()                # run garbage collection right away
0
>>> d['primary']                # entry was automatically removed
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    d['primary']                # entry was automatically removed
  File "C:/python316/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

۱۱٫۷. ابزارهایی برای کار با فهرست‌ها

بسیاری از نیازهای ساختار داده را می‌توان با نوع فهرست درون‌ساخت برآورده کرد. با این حال، گاهی نیاز به پیاده‌سازی‌های جایگزین با مصالحه‌های کارایی متفاوت هست.

ماژول array یک شیء array فراهم می‌کند که مانند فهرستی است که فقط دادهٔ همگن را ذخیره می‌کند و آن را فشرده‌تر ذخیره می‌کند. مثال زیر آرایه‌ای از اعداد را نشان می‌دهد که به‌صورت اعداد دودویی بدون علامت دو بایتی (typecode "H") ذخیره شده‌اند، به‌جای ۱۶ بایت معمول به ازای هر ورودی برای فهرست‌های معمولی از اشیای int پایتون:

>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])

ماژول collections یک شیء deque فراهم می‌کند که مانند فهرستی است با افزودن و برداشتن سریع‌تر از سمت چپ اما جست‌وجوهای کندتر در وسط. این اشیا برای پیاده‌سازی صف‌ها و جست‌وجوهای عرض‌اول درخت مناسب‌اند:

>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Handling", d.popleft())
Handling task1
unsearched = deque([starting_node])
def breadth_first_search(unsearched):
    node = unsearched.popleft()
    for m in gen_moves(node):
        if is_goal(m):
            return m
        unsearched.append(m)

علاوه بر پیاده‌سازی‌های جایگزین فهرست، کتابخانه ابزارهای دیگری مانند ماژول bisect با توابعی برای دستکاری فهرست‌های مرتب‌شده هم ارائه می‌دهد:

>>> import bisect
>>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(scores, (300, 'ruby'))
>>> scores
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

ماژول heapq توابعی برای پیاده‌سازی هیپ‌ها (heaps) بر پایهٔ فهرست‌های معمولی فراهم می‌کند. ورودی با کمترین مقدار همیشه در موقعیت صفر نگه داشته می‌شود. این برای برنامه‌هایی مفید است که مکرراً به کوچک‌ترین عنصر دسترسی دارند اما نمی‌خواهند مرتب‌سازی کامل فهرست انجام دهند:

>>> from heapq import heapify, heappop, heappush
>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data)                      # rearrange the list into heap order
>>> heappush(data, -5)                 # add a new entry
>>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
[-5, 0, 1]

۱۱٫۸. محاسبات ممیز شناور اعشاری

ماژول decimal یک نوع داده Decimal برای محاسبات ممیز شناور اعشاری ارائه می‌دهد. در مقایسه با پیاده‌سازی درون‌ساخت float از ممیز شناور دودویی، این کلاس به‌ویژه برای موارد زیر مفید است:

  • برنامه‌های مالی و کاربردهای دیگر که به نمایش اعشاری دقیق نیاز دارند،
  • کنترل دقت،
  • کنترل گرد کردن برای برآورده کردن الزامات قانونی یا نظارتی،
  • ردیابی ارقام اعشاری معنادار، یا
  • برنامه‌هایی که در آن‌ها کاربر انتظار دارد نتایج با محاسبات انجام‌شده با دست مطابقت داشته باشند.

برای مثال، محاسبهٔ ۵٪ مالیات روی یک هزینهٔ تلفن ۷۰ سنتی در ممیز شناور اعشاری و ممیز شناور دودویی نتایج متفاوتی می‌دهد. اگر نتایج به نزدیک‌ترین سنت گرد شوند، تفاوت معنادار می‌شود:

>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73

نتیجهٔ Decimal یک صفر انتهایی را نگه می‌دارد و به‌طور خودکار اهمیت چهار رقمی را از ضرب‌شوندگانی با اهمیت دو رقمی استنباط می‌کند. Decimal ریاضیات انجام‌شده با دست را بازتولید می‌کند و از مشکلاتی که وقتی ممیز شناور دودویی نمی‌تواند مقادیر اعشاری را دقیقاً نمایش دهد پیش می‌آید دوری می‌کند.

نمایش دقیق به کلاس Decimal امکان می‌دهد محاسبات باقی‌مانده (modulo) و آزمون‌های برابری‌ای انجام دهد که برای ممیز شناور دودویی نامناسب‌اند:

>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
False

ماژول decimal محاسبات را با هر دقتی که لازم باشد فراهم می‌کند:

>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')