Краткий обзор стандартной библиотеки — часть II
Во второй части обзора рассматриваются более продвинутые модули, предназначенные для решения профессиональных задач программирования. Эти модули редко используются в небольших скриптах.
11.1. Форматирование вывода
Модуль reprlib предоставляет версию repr(), предназначенную для сокращённого отображения больших или глубоко вложенных контейнеров:
>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"
Модуль pprint предлагает более гибкие средства управления выводом встроенных объектов и объектов, определённых пользователем, в формате, понятном интерпретатору. Если результат занимает больше одной строки, «форматирующий вывод» добавляет переводы строк и отступы, чтобы нагляднее показать структуру данных:
>>> 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 позволяет напрямую форматировать числа с разделителями групп разрядов:
>>> 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 с упрощённым синтаксисом, подходящим для редактирования конечными пользователями. Это позволяет пользователям настраивать приложения, не изменяя их.
В формате используются имена заполнителей, образованные с помощью $ и допустимых идентификаторов Python (буквенно-цифровых символов и знаков подчёркивания). Если заключить заполнитель в фигурные скобки, за ним могут следовать другие буквенно-цифровые символы без пробелов. Запись $$ создаёт один экранированный символ $:
>>> 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, если значение заполнителя не указано в словаре или в именованном аргументе. В приложениях для слияния почты предоставленные пользователем данные могут быть неполными, поэтому метод 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() для работы с двоичными записями переменной длины. В следующем примере показано, как перебрать заголовки ZIP-файла, не используя модуль zipfile. Коды упаковки "H" и "I" обозначают беззнаковые числа длиной два и четыре байта соответственно. "<" указывает на стандартный размер и порядок байтов от младшего к старшему:
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):
threading.Thread.__init__(self)
self.infile = infile
self.outfile = outfile
def run(self):
f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
f.write(self.infile)
f.close()
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.
Систему ведения журнала можно настроить непосредственно из Python или загрузить настройки из редактируемого пользователем файла конфигурации, чтобы изменять журналирование без изменения приложения.
11.6. Слабые ссылки
Python автоматически управляет памятью (в большинстве случаев с помощью подсчёта ссылок, а также с помощью сборки мусора для устранения циклов). Память освобождается вскоре после удаления последней ссылки на объект.
Этот подход хорошо работает в большинстве приложений, однако иногда требуется отслеживать объекты, только пока они используются чем-либо ещё. К сожалению, само отслеживание создаёт ссылку, из-за которой объект остаётся в памяти. Модуль 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:/python312/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Средства для работы со списками
Многие задачи по работе со структурами данных можно решить с помощью встроенного типа list. Однако иногда нужны альтернативные реализации с другими компромиссами между производительностью и затратами ресурсов.
Модуль array предоставляет объект array, похожий на список, но хранящий только однородные данные в более компактном виде. В следующем примере показан массив чисел, сохранённых в виде беззнаковых двоичных чисел длиной два байта (код типа "H"), вместо обычных 16 байт на элемент для списков объектов int в Python:
>>> 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 предоставляет функции для реализации куч на основе обычных списков. Элемент с наименьшим значением всегда хранится в позиции ноль. Это полезно в приложениях, которые часто обращаются к наименьшему элементу, но не хотят полностью сортировать список:
>>> 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, этот класс особенно полезен для
- финансовых приложений и других задач, требующих точного десятичного представления;
- управления точностью;
- управления округлением в соответствии с требованиями законодательства или нормативных актов;
- отслеживания значащих десятичных разрядов;
- приложений, в которых пользователь ожидает, что результаты будут совпадать с вычислениями, выполненными вручную.
Например, расчёт налога в размере 5% на телефонный разговор стоимостью 70 центов даёт разные результаты при использовании десятичной и двоичной арифметики с плавающей точкой. Разница становится существенной, если округлить результаты до ближайшего цента:
>>> 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')
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/tutorial/stdlib2.html