Підручник мови Python/Короткий огляд стандартної бібліотеки II

Ця друга частина охоплює більш складні модулі, призначені для потреб професійного програмування. Ці модулі рідко зустрічаються у маленьких скриптах.

Форматування виводу ред.

Модуль repr має версію функції repr() для скороченого зображення великих або багаторівневих структур даних:

>>> import repr   
>>> repr.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 = """Метод wrap() подібний до fill(), але він повертає
... список рядків замість одного довгого рядка, розбитого 
... на рядки."""
...
>>> print textwrap.fill(doc, width=40)
Метод wrap() подібний до fill(), 
але він повертає список рядків 
замість одного довгого рядка, 
розбитого на рядки.

Модуль locale завантажує формати даних, специфічні для певного культурного оточення. Спеціальний атрибут форматуючої функції модуля надає можливість прямого форматування чисел за допомогою групових роздільників:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'uk_UA.utf8')
('uk_UA', 'utf8')
>>> conv = locale.localeconv()   # отримати правила переведення
>>> x = 1234567.8
>>> locale.format("%d", x, grouping=True)
'1.234.567'
>>>  print locale.format("%.*f%s", 
...         (conv['int_frac_digits'], x, 
...         conv['currency_symbol']), grouping=True)
1.234.567,80гр

Шаблони ред.

Модуль string має зручний клас Template з досить простим синтаксисом, придатним для редагування користувачами. За його допомогою користувачі можуть змінювати текстові величини програми без внесення змін до її коду.

Формат модуля використовує спеціальні назви-заповнювачі, що утворюються за допомогою символа "$" та дійсного ідентифікатора мови Пайтон (буквено-цифрові символи та нижня риска). Для відокремлення назви-заповнювача від наступних буквено-цифрових символів слід використовувати фігурні дужки. "$$" задає один символ "$":

>>> from string import Template
>>> t = Template('${village} витратили $$10 на $cause.')
>>> print t.substitute(village='Васюки', cause='сміттєфонд')
Васюки витратили $10 на сміттєфонд.

Метод substitute відкидає KeyError якщо ключове слово не існує в словнику або в ключовому аргументі. Для програм, де дані для заповнення шаблонів можуть бути неповними, краще використовувати метод safe_substitute, що за умови відсутності відповідних даних залишить незаповнені назви без змін.

>>> t = Template('Повернути $item $owner.')
>>> d = dict(item='непроковтнутий шматок')
>>> t.substitute(d)
Traceback (most recent call last):
  . . .
KeyError: 'owner'
>>> print t.safe_substitute(d)
Повернути непроковтнутий шматок $owner.

Похідні класи-шаблони можуть визначати свої власні роздільники. Наприклад, спеціальний утиліт для зміни назви групи файлів, що є частиною фото-переглядача, може використовувати символ процента для позначення таких назв-заповнювачів як, скажімо, число, номер послідовності чи формат файла:

>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
>>> fmt = raw_input('Введіть формат зміни назви (%d-число %n-номер %f-формат):  ')
Введіть формат зміни назви (%d-число %n-номер %f-формат):  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 '%s --> %s' % (filename, newname)

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

Інше можливе використання шаблонів — це відокремлення логіки програми від деталей численних форматів виводу (наприклад, простого тексту, XML, HMTL тощо).

Робота з форматами бінарних записів даних ред.

Модуль struct має функції pack() та unpack(), що призначені для роботи з бінарними записами різної величини. Наступний приклад показує, як можна зчитати інформацію із заголовка файла у форматі ZIP (коди пакування "H" та "L" відповідно виражають три- та чотирибайтові беззнакові числа):

    import struct

    data = open('myfile.zip', 'rb').read()
    start = 0
    for i in range(3):      # три перші заголовки файла
        start += 14
        fields = struct.unpack('LLLHH', 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) — це технологія відокремлення послідовно незалежних задач. Галузки можуть використовуватися для покращення ефективності програм, що отримують дані від користувача під час виконання інших задач. Ще одне можливе застосування — здійснення вводу-виводу під час паралельного обчислення іншою галузкою.

Наступний код ілюструє, як модуль високого рівня 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 'Закінчено фонове ущільнення файла ', self.infile

    background = AsyncZip('mydata.txt', 'myarchive.zip')
    background.start()
    print 'Головна програма продовжує виконуватися на передньому фоні.'
    
    background.join()    # Чекаємо закінчення виконання фонової задачі
    print 'Головна програма дочекалася завершення фонової задачі.'

Головна складність при розгалуженні програми полягає в координації окремих галузок, що поділяють дані чи інші ресурси. Для цього модуль threading має кілька примітивних функцій синхронізації, що включають замки, події, умовні змінні та семафори.

Ці інструменти дуже потужні, але навіть невеликі помилки дизайну можуть призвести до проблем, які згодом буде важко відтворити. Таким чином, найкращим способом для координації задач є зосередження доступу до ресурсів у єдиній галузці з подальшим використанням модуля Queue для передачі цій галузці запитів від інших галузок. Програми, що використовують для координації і комунікації між галузками об'єкти типу Queue, відзначаються відносною простотою дизайну, читабельністю та надійністю.

Журнальні записи ред.

Модуль logging пропонує гнучку і потужну систему для створення журнальних записів. У найпростішому випадку журнальні повідомлення виводяться у файл чи на стандартний вивід для запису помилок (sys.stderr):

    import logging
    logging.debug('Інформація по зневадженню')
    logging.info('Інформаційне повідомлення')
    logging.warning('Попередження: конфігураційний файл %s не знайдено', 'server.conf')
    logging.error('Відбулася помилка')
    logging.critical('Критична помилка -- програма зачиняється')

Це видає такий вивід:

    WARNING:root:Попередження: конфігураційний файл server.conf не знайдено
    ERROR:root:Відбулася помилка
    CRITICAL:root:Критична помилка -- програма зачиняється

Загалом інформаційні повідомлення та повідомлення по налагодженню замовчуються, а вивід здійснюється на стандартний потік для виводу помилок. Інші можливості виводу включають передачу повідомлень через електронну пошту, датаграми, сокети або HTTP-сервер. Повідомлення можуть отримувати різні маршрути в залежності від пріоритету повідомлень: DEBUG (зневадження), INFO (загальна інформація), WARNING (попередження), ERROR (помилка) та CRITICAL (критичне повідомлення).

Система ведення журнальних записів може бути налаштована напряму з Пайтона або завантажена з відредагованого користувачем конфігураційного файла без внесення жодних змін до існуючих програм.

Слабкі посилання ред.

Пайтон автоматично керує пам'яттю за допомогою підрахунку посилань для більшості об'єктів та автоматичного смітника для видалення циклів. Пам'ять автоматично звільняється незабаром після видалення останнього посилання.

Такий підхід задовольняє більшість програм, але інколи потрібно утримувати об'єкти лише тоді, коли вони десь використовуються. На жаль, створення ще одного посилання робить ці об'єкти постійними. Модуль 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 "pyshell#108", line 1, in -toplevel-
        d['primary']                # елемент автоматично видаляється
      File "C:/PY24/lib/weakref.py", line 46, in __getitem__
        o = self.data[key]()
    KeyError: 'primary'

Знаряддя для роботи зі списками ред.

Вбудовані списки можуть використовуватися у різноманітних структурах даних. Але інколи потрібно застосовувати альтернативні рішення, які можуть вплинути на ефективність виконання програми.

Модуль array містить об'єкт array() для створення масивів, що подібний до списку, який містить лише однорідні дані, і який зберігає їх більш компактно. Подальший приклад ілюструє масив чисел, що зберігається у вигляді двобайтових беззнакових бінарних чисел (код "H") замість звичайних 16-байтових елементів у списках цілочислових величин мови Пайтон.

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

Модуль collections містить об'єкт deque, (double-ended queue — "черга з двома кінцями"), що подібний до списку з пришвидшеним видаленням і додаванням до його лівої частини, але повільнішим пошуком елементів, розташованих посередині. Ці об'єкти призначені для створення черг та дерев, де пошук відбувається спочатку в ширину, а потім в глибину.

    >>> from collections import deque
    >>> d = deque(["задача1", "задача2", "задача3"])
    >>> d.append("задача4")
    >>> print "Опрацьовується", d.popleft()
    Опрацьовується задача1

    # пошук в ширину
    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 містить функції для створення стосів (heaps) на основі звичайних списків. Елемент, що має найменшу величину, завжди знаходиться в нульовій позиції. Це корисно, коли потрібен постійний доступ до найменшого елемента, але при цьому ви не хочете сортувати весь список.

    >>> 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), цей новий клас особливо корисний при розробці фінансових та інших програм, що потребують точної десяткової репрезентації, контролю округлення для задоволення різних правових чи цивільних вимог, збереження значущих десяткових знаків, або тоді, коли результати комп'ютерного обчислення повинні відповідати підрахунку, зробленому на папері.

Наприклад, підрахунок п'ятивідсоткового податку на телефонну розмову вартістю 70 копійок дає відмінні результати при використанні двійкових та десяткових дробів. Ця різниця стає істотною при округленні до однієї копійки.

>>> from decimal import *       
>>> Decimal('0.70') * Decimal('1.05')
Decimal("0.7350")
>>> .70 * 1.05
0.73499999999999999

Результат цього обчислення, що належить до типу 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")