11. Краткий обзор стандартной библиотеки - Часть II

Этот второй тур охватывает более продвинутые модули, которые поддерживают профессиональные потребности программирования. Эти модули редко встречаются в небольших скриптах.

11.1. Форматирование вывода

Модуль reprlib предоставляет версию repr(), настроенную для укороченных отображений больших или глубоко вложенных контейнеров:

>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"set(['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.'

Подклассы 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):                      # показывает первые 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. Многопоточность

Поточность представляет собой метод разъединения задач, которые можно выполнять не последовательно. Потоки могут быть использованы для улучшения быстродействия приложений, которые принимают пользовательский ввод, в то время как другие задачи выполняются в фоновом режиме. С этим связан случай использования запущенного ввода/вывода параллельно с вычислениями в другом потоке.

Следующий код показывает, как высокоуровневый модуль 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 предлагает полнофункциональную и гибкую систему регистрации. В самом простом случае, log-сообщения отправляются в файл или в 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)                   # создает ссылку
>>> 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:/python33/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

11.7. Инструменты для работы со списками

Многие потребности структуры данных могут быть удовлетворены с помощью встроенного спискового типа. Тем не менее, иногда возникает потребность в альтернативных реализациях с учетом различных компромиссов производительности.

Модуль 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)                      # переделывание списка в порядок кучи
>>> heappush(data, -5)                 # добавляет новую запись
>>> [heappop(data) for i in range(3)]  # приносит три наименьшие записи
[-5, 0, 1]

11.8. Десятичная арифметика с плавающей точкой

Модуль decimal (docs.python.org/3/library/decimal.html#module-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')

Создано

Обновлено