Python پایتون ۳.۱۴
فصل ۱۱

گشتی کوتاه در کتابخانهٔ استاندارد — بخش دوم

📖 ۱۴ دقیقه 🎯 متوسط 🔄 ۲۰۲۶

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

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

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

>>> 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 در تابع format این پیمانه، راهی مستقیم برای قالب‌بندی اعداد با جداکننده‌های گروه فراهم می‌کند:

>>> 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 همه‌کاره با نحوی ساده‌شده است که برای ویرایش توسط کاربران نهایی مناسب می‌باشد. این امر به کاربران اجازه می‌دهد تا برنامه‌های خود را بدون نیاز به تغییر کد برنامه سفارشی‌سازی کنند.

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

>>> 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 می‌توانند یک جداکننده (delimiter) سفارشی مشخص کنند. برای مثال، یک ابزار تغییر نام دسته‌ای برای یک مرورگر عکس ممکن است انتخاب کند که از علامت درصد برای جای‌نگهدارهایی مانند تاریخ جاری، شماره توالی تصویر، یا فرمت فایل استفاده کند:

>>> 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 یک سیستم ثبت وقایع کامل و انعطاف‌پذیر ارائه می‌دهد. در ساده‌ترین حالت، پیام‌های لاگ به یک فایل یا به 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

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

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

۱۱.۶. ارجاعات ضعیف (Weak References)

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

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

>>> 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:/python314/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 فراهم می‌کند که شبیه به لیست است اما الحاق (append) و حذف (pop) سریع‌تری از سمت چپ دارد، در حالی که جست‌وجو در وسط آن کندتر است. این اشیاء برای پیاده‌سازی صف‌ها و جست‌وجوی درخت به روش اول-عمق (breadth first tree searches) بسیار مناسب هستند:

>>> 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 توابعی برای پیاده‌سازی هیپ (heap) بر اساس لیست‌های عادی فراهم می‌کند. کم‌ارزش‌ترین ورودی همیشه در موقعیت صفر نگه داشته می‌شود. این برای برنامه‌هایی که مکرراً به کوچک‌ترین عنصر دسترسی دارند اما نمی‌خواهند یک مرتب‌سازی کامل لیست را اجرا کنند، مفید است:

>>> 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 floating-point arithmetic)

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

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

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

>>> 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')