11. مروری کوتاه بر کتابخانهی استاندارد — بخش دوم¶
این بخش دوم از مرور، ماژولهای پیشرفتهتری را معرفی میکند که نیازهای برنامهنویسی حرفهای را پوشش میدهند. این ماژولها بهندرت در اسکریپتهای کوچک استفاده میشوند.
11.1. قالببندی خروجی¶
ماژول 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 به پایگاه دادهای از قالبهای دادهی وابسته به زبان و 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'
11.2. قالبهای متنی¶
ماژول string شامل کلاس انعطافپذیر Template با نحوی ساده است که برای ویرایش توسط کاربران نهایی مناسب است. این امکان را فراهم میکند که کاربران بتوانند برنامههای خود را بدون تغییر دادن خود برنامه سفارشیسازی کنند.
در این قالب، نام جاینگهدارها با استفاده از $ و شناسههای معتبر پایتون (شامل حروف، ارقام و زیرخط) ساخته میشوند. اگر جاینگهدار را درون آکولاد قرار دهید، میتوانید بدون فاصله، بلافاصله پس از آن حروف یا ارقام بیشتری بیاورید. همچنین، نوشتن $$ یک نویسهی $ فراردهیشده ایجاد میکند:
>>> 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 را فراهم میکند.
11.3. کار با ساختار رکوردهای دادهی دودویی¶
ماژول 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
11.4. چندنخی¶
چندنخی روشی برای جدا کردن وظایفی است که بهصورت ترتیبی به یکدیگر وابسته نیستند. از نخها میتوان برای افزایش پاسخگویی برنامههایی استفاده کرد که همزمان با اجرای وظایف دیگر در پسزمینه، ورودی کاربر را نیز دریافت میکنند. یکی دیگر از کاربردهای رایج آن، اجرای عملیات ورودی/خروجی بهصورت موازی با محاسبات در نخ دیگر است.
کد زیر نشان میدهد که چگونه ماژول سطحبالای 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 مجموعهای از سازوکارهای همگامسازی، از جمله قفلها، رویدادها، متغیرهای شرطی و سمافورها را فراهم میکند.
با وجود قدرت این ابزارها، حتی خطاهای کوچک در طراحی میتوانند مشکلاتی ایجاد کنند که بازتولید آنها دشوار باشد. بنابراین، روش ترجیحی برای هماهنگی وظایف این است که تمام دسترسیها به یک منبع در یک نخ متمرکز شوند و سپس از ماژول queue برای ارسال درخواستهای سایر نخها به آن استفاده شود. برنامههایی که برای ارتباط و هماهنگی میان نخها از اشیای Queue استفاده میکنند، طراحی سادهتر، خوانایی بیشتر و قابلیت اطمینان بالاتری دارند.
11.5. گزارشگیری¶
ماژول 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
بهطور پیشفرض، پیامهای اطلاعرسانی و اشکالزدایی نمایش داده نمیشوند و خروجی به خطای استاندارد ارسال میشود. گزینههای دیگر برای خروجی شامل ارسال پیامها از طریق ایمیل، دیتاگرامها، سوکتها یا یک سرور HTTP است. همچنین میتوان با استفاده از فیلترها، پیامها را بر اساس سطح اهمیت آنها هدایت کرد: DEBUG، INFO، WARNING، ERROR و CRITICAL.
سامانهی گزارشگیری را میتوان مستقیماً از داخل پایتون پیکربندی کرد یا تنظیمات آن را از یک پرونده پیکربندی قابل ویرایش توسط کاربر بارگذاری نمود تا بدون تغییر برنامه، رفتار گزارشگیر سفارشیسازی شود.
11.6. ارجاعهای ضعیف¶
پایتون مدیریت حافظه را بهصورت خودکار انجام میدهد (برای بیشتر اشیا با استفاده از شمارش ارجاعها و زبالهروبی (جمعآوری زباله) برای حذف چرخهها). حافظه اندکی پس از حذف آخرین ارجاع به یک شی آزاد میشود.
این روش برای بیشتر برنامهها مناسب است، اما گاهی لازم است اشیا فقط تا زمانی دنبال شوند که توسط چیز دیگری در حال استفاده باشند. مشکل اینجاست که صرفِ دنبال کردن یک شی، خود یک ارجاع ایجاد میکند و باعث میشود آن شی هرگز آزاد نشود. ماژول weakref ابزارهایی برای دنبال کردن اشیا بدون ایجاد ارجاع فراهم میکند. هنگامی که دیگر نیازی به یک شی نباشد، آن شی بهطور خودکار از جدول weakref حذف میشود و یک callback برای اشیای weakref فراخوانی میشود. یکی از کاربردهای رایج این قابلیت، ذخیرهی موقت (cache) اشیایی است که ایجاد آنها پرهزینه است:
>>> 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'
11.7. ابزارهایی برای کار با فهرستها¶
بسیاری از نیازهای مربوط به ساختارهای داده را میتوان با استفاده از نوع توکار list برآورده کرد. با این حال، گاهی به پیادهسازیهای جایگزینی نیاز است که توازن متفاوتی میان کارایی و عملکرد ارائه میدهند.
ماژول array یک شیء array فراهم میکند که مانند یک فهرست است، اما فقط دادههای همگن را ذخیره میکند و آنها را فشردهتر ذخیره میکند. نمونهی زیر آرایهای از اعداد را نشان میدهد که بهجای ۱۶ بایت معمول برای هر ورودی در فهرستهای معمولی از اشیای int پایتون، بهصورت اعداد دودویی بدون علامت دوبایتی (کدنوع "H") ذخیره شده است:
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])
ماژول collections شیء deque را فراهم میکند که شبیه یک فهرست است، اما افزودن و حذف عنصر از ابتدای آن بسیار سریعتر و دسترسی به عناصر میانی آن کندتر است. این اشیا برای پیادهسازی صفها و پیمایش درخت به روش جستوجوی سطحبهسطح (Breadth-First Search) بسیار مناسب هستند:
>>> 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 توابعی برای پیادهسازی هیپ بر پایهی فهرستهای معمولی فراهم میکند. در این ساختار، کوچکترین عنصر همیشه در اندیس صفر قرار میگیرد. این ویژگی برای برنامههایی مفید است که بهطور مکرر به کوچکترین عنصر نیاز دارند، اما نمیخواهند هر بار کل فهرست را مرتب کنند:
>>> 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]
11.8. محاسبات اعشاری با ممیز شناور دهدهی¶
ماژول 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 این امکان را میدهد که محاسبات باقیمانده و آزمونهای برابری را بهدرستی انجام دهد؛ عملیاتی که استفاده از آنها با ممیز شناور دودویی مناسب نیست:
>>> 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')