руководствоPython
August 9, 2020

Краткий экскурс по стандартной библиотеке — Часть 2

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

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

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 Модуль обращается к базе данных о культуре специфических форматов данных. Атрибут группировки функции форматирования локали обеспечивает прямой способ форматирования чисел с помощью разделителей групп:

>>> 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'

Шаблонирование

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.

Работа с макетами записи двоичных данных

В 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):                      # показывает заголовки 4 первых файлов
    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     #прейти к следующему

Многопоточность

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

В следующем коде показано, как 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.')

Основная проблема многопоточных приложений - это координация потоков, которые совместно используют данные или другие ресурсы. С этой целью модуль потоковой передачи предоставляет ряд примитивов синхронизации, включая блокировки, события, переменные условий и семафоры.

Хотя эти инструменты являются мощными, незначительные ошибки проектирования могут привести к проблемам, которые трудно воспроизвести. Таким образом, предпочтительный подход к координации задач - сосредоточить весь доступ к ресурсу в одном потоке, а затем использовать queue модуль для подачи в этот поток запросов из других потоков. Приложения, использующие Queue объекты для межпоточного взаимодействия и координации, проще разрабатывать, они более читабельны и надежны.

Логирование

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-сервер. Новые фильтры могут выбирать различные маршрутизации в зависимости от приоритета сообщения: DEBUGINFOWARNINGERROR, и CRITICAL.

Систему ведения журнала можно настроить непосредственно из Python или загрузить из редактируемого пользователем файла конфигурации для индивидуального ведения журнала без изменения приложения.

Слабые ссылки

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:/python38/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

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

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

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]

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

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')

Заключение

Пост был создан для тг-канала @coolcoders