# 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 функції форматування 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_string("%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"` представляють дво- та чотирибайтові числа без знаку відповідно. `"<"` вказує, що вони мають стандартний розмір і порядок байтів у порядку байтів:

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

## 11.7. Інструменти для роботи зі списками

Багато потреб у структурі даних можна задовольнити за допомогою вбудованого типу списку. Однак іноді виникає потреба в альтернативних реалізаціях з іншими компромісами щодо продуктивності.

The `array` module provides an `array` object that is like a list that stores only homogeneous data and stores it more compactly. The following example shows an array of numbers stored as two byte unsigned binary numbers (typecode `"H"`) rather than the usual 16 bytes per entry for regular lists of Python int objects:

    >>> from array import array
    >>> a = array('H', [4000, 10, 700, 22222])
    >>> sum(a)
    26932
    >>> a[1:3]
    array('H', [10, 700])

The `collections` module provides a `deque` object that is like a list with faster appends and pops from the left side but slower lookups in the middle. These objects are well suited for implementing queues and breadth first tree searches:

    >>> 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 Floating-Point Arithmetic

The `decimal` module offers a `Decimal` datatype for decimal floating-point arithmetic. Compared to the built-in `float` implementation of binary floating point, the class is especially helpful for

- фінансові програми та інші види використання, які вимагають точного десяткового представлення,

- контроль над точністю,

- контроль за округленням відповідно до правових або нормативних вимог,

- відстеження значущих знаків після коми, або

- програми, де користувач очікує, що результати відповідатимуть обчисленням, зробленим вручну.

Наприклад, обчислення 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
    >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
    False

Модуль `decimal` забезпечує арифметику з необхідною точністю:

    >>> getcontext().prec = 36
    >>> Decimal(1) / Decimal(7)
    Decimal('0.142857142857142857142857142857142857')