Содержание страницы
11. Краткий обзор стандартной библиотеки – Часть II¶Brief Tour of the Standard Library – Part II
Этот второй обзор охватывает более продвинутые модули, поддерживающие потребности профессионального программирования. Такие модули редко встречаются в небольших скриптах.
11.1. Форматирование вывода¶Output Formatting
Модуль reprlib предоставляет версию repr(), адаптированную для сокращённого отображения больших или глубоко вложенных контейнеров:
>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"set(['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 модуля locale предоставляет прямой способ форматирования чисел с разделителями групп:
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv() # получить отображение соглашений
>>> 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. Шаблоны¶Templating
Модуль 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. Работа с двоичными структурами данных¶Working with Binary Data Record Layouts
Модуль struct предоставляет функции pack() и unpack() для работы с бинарными форматами записей переменной длины. Следующий пример показывает, как пройтись по заголовкам ZIP-файла без использования модуля zipfile. Коды упаковки "H" и "I" представляют двух- и четырёхбайтовые беззнаковые числа соответственно. "<" указывает, что они имеют стандартный размер и порядок байтов от младшего к старшему:
import struct
data = open('myfile.zip', 'rb').read()
start = 0
for i in range(3): # показать первые 3 заголовка файла
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 # перейти к следующему заголовку
11.4. Многопоточность¶Multi-threading
Многопоточность – это техника разделения задач, не зависящих последовательно друг от друга. Потоки можно использовать для повышения отзывчивости приложений, принимающих пользовательский ввод, пока другие задачи выполняются в фоне. Смежный случай использования – выполнение операций ввода-вывода параллельно с вычислениями в другом потоке.
Следующий код показывает, как высокоуровневый модуль 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() # Дождаться завершения фоновой задачи
print('Main program waited until background was done.')
Основная сложность многопоточных приложений – координация потоков, которые совместно используют данные или другие ресурсы. Для этого модуль threading предоставляет ряд примитивов синхронизации, включая блокировки, события, условные переменные и семафоры.
Хотя эти инструменты мощные, небольшие ошибки проектирования могут привести к проблемам, которые трудно воспроизвести. Поэтому предпочтительный подход к координации задач – сосредоточить весь доступ к ресурсу в одном потоке, а затем использовать модуль queue для подачи запросов от других потоков в этот поток. Приложения, использующие объекты Queue для межпоточного взаимодействия и координации, проще проектировать, они более читаемы и надёжны.
11.5. Журналирование¶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
По умолчанию информационные и отладочные сообщения подавляются, а вывод направляется в стандартный поток ошибок. Другие варианты вывода включают маршрутизацию сообщений по электронной почте, через датаграммы, сокеты или на HTTP-сервер. Новые фильтры могут выбирать различную маршрутизацию в зависимости от приоритета сообщения: DEBUG, INFO, WARNING, ERROR и CRITICAL.
Система логирования может быть настроена непосредственно из Python или загружена из редактируемого пользователем конфигурационного файла для индивидуальной настройки без изменения приложения.
11.6. Слабые ссылки¶Weak References
Python выполняет автоматическое управление памятью (подсчёт ссылок для большинства объектов и сборка мусора для устранения циклов). Память освобождается вскоре после удаления последней ссылки на неё.
Этот подход хорошо работает для большинства приложений, но иногда возникает необходимость отслеживать объекты только до тех пор, пока они используются чем-то другим. К сожалению, простое отслеживание создаёт ссылку, которая делает их постоянными. Модуль weakref предоставляет инструменты для отслеживания объектов без создания ссылки. Когда объект больше не нужен, он автоматически удаляется из таблицы слабых ссылок, и для объектов weakref вызывается колбэк. Типичные применения включают кэширование объектов, создание которых дорого обходится:
>>> import weakref, gc
>>> class A:
... def __init__(self, value):
... self.value = value
... def __repr__(self):
... return str(self.value)
...
>>> a = A(10) # создать ссылку
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a # не создаёт ссылку
>>> d['primary'] # получить объект, если он ещё существует
10
>>> del a # удалить единственную ссылку
>>> gc.collect() # запустить сборку мусора немедленно
0
>>> d['primary'] # запись была автоматически удалена
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # запись была автоматически удалена
File "C:/python31/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Инструменты для работы со списками¶Tools for Working with Lists
Многие потребности в структурах данных можно удовлетворить с помощью встроенного типа list. Однако иногда возникает необходимость в альтернативных реализациях с другими компромиссами по производительности.
Модуль 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) # перестроить список в порядке кучи
>>> heappush(data, -5) # добавить новую запись
>>> [heappop(data) for i in range(3)] # извлечь три наименьших элемента
[-5, 0, 1]
11.8. Десятичная арифметика с плавающей запятой¶Decimal Floating Point Arithmetic
Модуль 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
>>> sum([0.1]*10) == 1.0
False
Модуль decimal предоставляет арифметику с требуемой точностью:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')