Краткий обзор стандартной библиотеки — Часть 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("%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" представляют соответственно двухамперные и четырёхбайтные беззнаковые числа. "<" указывает, что они имеют стандартный размер и порядок байтов 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):
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 предоставляет инструменты для отслеживания объектов без создания ссылки. Когда объект больше не нужен, он автоматически удаляется из таблицы weakref, и вызывается обратный вызов для объектов 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:/python38/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Инструменты для работы со списками
Многие потребности в структурах данных могут быть удовлетворены встроенным типом списка. Однако иногда требуется альтернативная реализация с различными компромиссами производительности.
Модуль array предоставляет объект array(), который похож на список, хранящий только однородные данные и хранящий их более компактно. Следующий пример демонстрирует массив чисел, хранящихся как двухамперные беззнаковые двоичные числа (код типа "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('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995
>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False
Модуль decimal предоставляет арифметику с необходимой точностью:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/tutorial/stdlib2.html