11. Краткий экскурс по стандартной библиотеке — Часть 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
обращается к базе данных форматов данных, специфичных для конкретной культуры. Атрибут grouping функции format языка 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
, если заполнитель не указан в словаре или аргументе ключевого слова. Для приложений в стиле 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.'
Подклассы шаблонов могут указывать пользовательский разделитель. Например, утилита пакетного переименования для фотобраузера может использовать знаки процентов для разделителей, таких как текущая дата, порядковый номер изображения или формат файла:
>>> 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.')
Основной проблемой многопоточных приложений является координация потоков, которые совместно используют данные или другие ресурсы. Для этого модуль потоков предоставляет ряд примитивов синхронизации, включая блокировки, события, переменные состояния и семафоры.
Хотя эти инструменты являются мощными, незначительные ошибки в проектировании могут привести к проблемам, которые трудно воспроизвести. Поэтому предпочтительным подходом к координации задач является сосредоточение всего доступа к ресурсу в одном потоке, а затем использование модуля 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 осуществляет автоматическое управление памятью (подсчет ссылок для большинства объектов и garbage collection для устранения циклов). Память освобождается вскоре после удаления последней ссылки на нее.
Такой подход отлично подходит для большинства приложений, но иногда возникает необходимость отслеживать объекты только до тех пор, пока они используются чем-то другим. К сожалению, простое отслеживание создает ссылку, которая делает их постоянными. Модуль 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:/python310/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')