Краткий обзор стандартной библиотеки — часть 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.'
Подклассы шаблонов могут задавать собственный разделитель. Например, утилита пакетного переименования файлов в программе просмотра фотографий может использовать знаки процента в качестве заполнителей для текущей даты, порядкового номера изображения или формата файла:
>>> 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):
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.
Систему ведения журнала можно настроить непосредственно из 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:/python314/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Инструменты для работы со списками
Для многих задач, связанных со структурами данных, подходит встроенный тип list. Однако иногда нужны альтернативные реализации с другими компромиссами между производительностью и затратами ресурсов.
Модуль array предоставляет объект array, похожий на список, но хранящий только данные одного типа и занимающий меньше места. В следующем примере показан массив чисел, хранящихся в виде двухбайтовых беззнаковых двоичных чисел (typecode "H"), а не занимающих обычные 16 байт на каждый элемент, как в списке объектов Python типа 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 предоставляет функции для реализации куч на основе обычных списков. Элемент с наименьшим значением всегда находится в позиции ноль. Это полезно в приложениях, которым нужно многократно обращаться к наименьшему элементу, но не требуется каждый раз полностью сортировать список:
>>> 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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/tutorial/stdlib2.html