Книга: Автостопом по Python
Назад: 8. Управление кодом и его улучшение
Дальше: 10. Манипуляции с данными

9. Программные интерфейсы

В этой главе мы сначала покажем, как применять Python для получения информации из API, которые используются для того, чтобы делиться данными между организациями. Затем мы опишем инструменты, которые многие организации, работающие с Python, применяют с целью поддержки коммуникации внутри своей инфраструктуры.

Мы уже рассмотрели поддержку конвейеров и очередей в Python между процессами в подразделе «Модуль multiprocessing» раздела «Скорость» главы 8. Для коммуникации между компьютерами требуется, чтобы обе стороны применяли заранее определенный набор протоколов: для Интернета применяется стек протоколов TCP/IP (). Вы можете реализовать протокол UDP самостоятельно с помощью сокетов (), Python предлагает библиотеки ssl (предоставляет обертки TLS/SSL для сокетов) и asyncio (для реализации асинхронных транспортов для протоколов TCP, UDP, TLS/SSL) (), а также конвейеры для подпроцессов.

Но большинство использует более высокоуровневые библиотеки, которые предоставляют клиенты, реализующие различные протоколы уровня приложения: ftplib, poplib, imaplib, nntplib, smtplib, telnetlib и xmlrpc. Все они предоставляют классы для обычных клиентов и клиентов, имеющих обертку TLS/SSL (urllib применяется для работы с запросами HTTP, но мы во многих случаях рекомендуем библиотеку Requests).

В первом разделе этой главы рассматриваются запросы HTTP: как получить данные из общедоступных API в Сети. Далее мы сделаем небольшое отступление и расскажем о сериализации в Python, а в третьем разделе опишем популярные инструменты для работы с сетями предприятий. Мы постараемся явно указывать, когда какой-то инструмент доступен только в Python 3. Если вы используете Python 2 и не можете найти модуль или класс, о котором мы говорим, рекомендуем взглянуть на этот список изменений между стандартными библиотеками Python 2 и Python 3: .

Веб-клиенты

Протокол передачи гипертекста (Hypertext Transfer Protocol, HTTP) — это протокол приложения, предназначенный для распределенных, объединенных информационных систем, использующих гипермедиа. Является основным способом обмена данными во Всемирной сети. Данный раздел посвящен вопросу получения данных из Интернета с помощью библиотеки Requests.

Модуль стандартной библиотеки Python urllib предоставляет большую часть функциональности HTTP, которая вам может понадобиться, но на низком уровне для нее характерно выполнение немало объема работы для решения относительно простых задач (вроде получения данных от сервера HTTPS, который требует аутентификации). В документации к модулю urllib.request говорится, чтобы вы использовали вместо него библиотеку Requests.

Requests работает со всеми запросами HTTP в Python, что позволяет выполнить бесшовную интеграцию с веб-сервисами. Нет необходимости добавлять вручную строки запроса в ваши URL или выполнять кодирование ваших данных для команды POST. Поддержание соединений (устойчивые соединения HTTP) и объединение соединений HTTP в пулы доступны благодаря классу request.sessions.Session, поддерживаемому библиотекой urllib3 (), которая встроена в библиотеку Requests (это значит, что вам не нужно устанавливать ее отдельно). Вы можете получить ее с помощью pip:

$ pip install requests

В документации к Requests () более подробно описывается все то, что мы будем рассматривать далее.

API для сети

Практически все — от Бюро переписи населения США до Национальной биб­лиотеки Нидерландов — имеют API; его вы можете использовать для получения данных, которыми они хотят поделиться. Некоторые из этих API, вроде Twitter и Facebook, позволяют вам (или приложениям, которые вы используете) модифицировать эти данные. Возможно, вы слышали о термине RESTful API. REST расшифровывается как representational state transfer («передача состояния представления») — это парадигма, на которой основан способ проектирования HTTP 1.1, но она не является стандартом, протоколом или требованием. Однако большинство поставщиков API для веб-сервисов следуют принципам проектирования RESTful.

Воспользуемся кодом для того, чтобы проиллюстрировать распространенные термины:

13927.png 

13932.png Метод является частью протокола HTTP. В RESTful API разработчик выбирает, какое действие предпримет сервер, и указывает его вам в документации к API. По адресу содержится список всех методов, самые распространенные из них GET, POST, PUT и DELETE. Зачастую «глаголы HTTP» соответствуют своим именам — получают, изменяют или удаляют данные.

13942.png Базовый URI является корнем API.

13953.png Клиенты будут указывать конкретный элемент, для которого им нужны данные.

13961.png Вы можете задать и другие типы мультимедиа.

Этот код выполнил запрос HTTP к ресурсу , который является бэкендом JSON для PyPI. Если вы взглянете на него в браузере, то увидите большую строку JSON. В библиотеке Requests возвращаемым значением для запроса HTTP будет объект типа Response:

>>> import requests

>>> response = requests.get('')

>>> type(response)

<class 'requests.models.Response'>

>>> response.ok

True

>>> response.text    # Эта команда возвращает весь текст ответа

>>> response.json()  # Эта команда преобразует текст ответа в словарь

PyPI вернул текст в формате JSON. Не существует правила, которое регулирует формат отправляемых данных, но во многих API используются JSON или XML.

Анализ JSON. Нотация объектов Javascript (Javascript Object Notation, JSON) полностью соответствует своему имени: используется для определения объектов в JavaScript. Библиотека Requests имеет встроенный анализатор JSON в объектах типа Response.

Библиотека json () может анализировать JSON, расположенный в строках или файлах, и помещать его в словарь Python (или список, если это вам так удобнее). Она также преобразует словари или списки Python в строки JSON. Например, в следующей строке содержатся данные JSON:

json_string = '{"first_name": "Guido", "last_name":"van Rossum"}'

Проанализировать ее можно следующим образом:

import json

parsed_json = json.loads(json_string)

Теперь вы можете использовать эти данные как словарь:

print(parsed_json['first_name'])

"Guido"

Вы также можете преобразовать следующий словарь в JSON:

d = {

    'first_name': 'Guido',

    'last_name': 'van Rossum',

    'titles': ['BDFL', 'Developer'],

}

print(json.dumps(d))

'{"first_name": "Guido", "last_name": "van Rossum",

  "titles": ["BDFL", "Developer"]}'

simplejson для ранних версий Python

Библиотека json была добавлена в Python 2.6. Если вы используете более раннюю версию Python, можете загрузить из PyPI библиотеку simplejson (/).

simplejson предоставляет такой же API, как и модуль стандартной библиотеки json, но обновляется чаще, чем Python. Разработчики, работающие с более старыми версиями Python, все еще могут использовать функциональность, доступную в библиотеке json, импортировав simplejson. Вы можете выбрать simplejson как полноценную замену для json следующим образом:

import simplejson as json

После импортирования библиотеки simplejson под именем json все предыдущие примеры будут работать так же, как если бы вы использовали стандартную библио­теку json.

Анализ XML

В стандартной библиотеке есть анализатор XML (методы parse() и fromstring() класса xml.etree.ElementTree), но this использует библиотеку Expat и создает объект ElementTree, сохраняющий структуру XML. Это значит, что требуется итерировать по нему и опрашивать потомков, дабы получить содержимое. Если нужно лишь получить данные, обратитесь к untangle или xmltodict. Вы можете установить их командой pip:

$ pip install untangle

$ pip install xmltodict

• untangle (). Принимает документ XML и возвращает объект Python, чья структура отражает узлы и атрибуты документа. Например, такой файл XML:

<?xml version="1.0" encoding="UTF-8"?>

<root>

    <child name="child1" />

</root>

можно загрузить следующим образом:

import untangle

obj = untangle.parse('path/to/file.xml')

можно получить имя элемента-потомка:

obj.root.child['name']  # is 'child1'

• xmltodict (). Преобразует XML в словарь. Например, такой файл XML:

<mydocument has="an attribute">

  <and>

    <many>elements</many>

    <many>more elements</many>

  </and>

  <plus a="complex">

    element as well

  </plus>

</mydocument>

можно загрузить в объект типа OrderedDict (из модуля collections стандартной библиотеки Python):

import xmltodict

with open('path/to/file.xml') as fd: doc = xmltodict.parse(fd.read())

можно получить доступ к элементам, атрибутам и значениям:

doc['mydocument']['@has']  # is u'an attribute'

doc['mydocument']['and']['many']  # is [u'elements', u'more elements']

doc['mydocument']['plus']['@a']  # is u'complex'

doc['mydocument']['plus']['#text']  # is u'element as well'

С помощью xmltodict можно преобразовать словарь обратно в XML, вызвав функцию unparse(). Она имеет потоковый режим, подходящий для обработки файлов, не помещающихся в память, а также поддерживает пространства имен.

Скраппинг сайтов

Сайты не всегда предлагают данные в удобных форматах вроде CSV или JSON, но HTML представляет собой структурированные данные — здесь вступает в дело скраппинг.

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

13983.png

По мере того как сайты предлагают свои API, они явно просят вас не использовать скраппинг: API открывает вам доступ только к тем данным, которыми владельцы сайта желают поделиться. Перед тем как начать скраппинг, прочтите условия использования целевого сайта и будьте законопослушным гражданином.

lxml

lxml (/) — это довольно обширная библиотека, написанная для выполнения быстрого анализа документов XML и HTML. Позволяет обрабатывать некоторый объем некорректной разметки.

Загрузите ее с помощью pip:

$ pip install lxml

Используйте метод requests.get, чтобы получить веб-страницу с данными, преобразуйте их с помощью модуля html и сохраните результат в дереве:

13996.png 

14003.png Это реальная веб-страница, и данные, которые мы показываем, тоже реальные (вы можете посетить эту страницу в браузере).

14012.png Мы используем свойство page.content, а не page.text, поскольку метод html.fromstring() неявно ожидает получить объект типа bytes.

Теперь дерево содержит весь файл HTML и имеет удобную структуру. Мы можем пойти двумя путями: использовать XPath () или CSSSelect (). Оба этих способа стандартные для указания пути с помощью дерева HTML, они определены и поддерживаются World Wide Web Consortium (W3C) и реализованы как модули в lxml. В этом примере мы используем XPath. Руководство по XPath () поможет вам начать работу.

Существуют различные инструменты для получения XPath элементов изнутри вашего браузера вроде Firebug for Firefox или Chrome Inspector. Если используете Chrome, щелкните правой кнопкой мыши на элементе, выберите пункт меню Inspect element (Инспектировать элемент), подсветите код, снова щелкните правой кнопкой и выберите Copy XPath (Скопировать XPath).

После небольшого анализа мы видим, что данные на нашей странице содержатся в двух элементах: div (с заголовком buyer-name) и span (имеющий класс item-price):

<div title="buyer-name">Carson Busses</div>

<span class="item-price">$29.95</span>

Зная это, мы можем создать корректный запрос XPath и использовать lxml-функцию xpath, как показано в примере:

# Это создаст список покупателей:

buyers = tree.xpath('//div[@title="buyer-name"]/text()')

# Это создаст список цен

prices = tree.xpath('//span[@class="item-price"]/text()')

Посмотрим, что получилось:

>>> print('Buyers: ', buyers)

Buyers:  ['Carson Busses', 'Earl E. Byrd', 'Patty Cakes',

'Derri Anne Connecticut', 'Moe Dess', 'Leda Doggslife', 'Dan Druff',

'Al Fresco', 'Ido Hoe', 'Howie Kisses', 'Len Lease', 'Phil Meup',

'Ira Pent', 'Ben D. Rules', 'Ave Sectomy', 'Gary Shattire',

'Bobbi Soks', 'Sheila Takya', 'Rose Tattoo', 'Moe Tell']

>>>

>>> print('Prices: ', prices)

Prices:  ['$29.95', '$8.37', '$15.26', '$19.25', '$19.25',

'$13.99', '$31.57', '$8.49', '$14.47', '$15.86', '$11.11',

'$15.98', '$16.27', '$7.50', '$50.85', '$14.26', '$5.68',

'$15.00', '$114.07', '$10.09']

Сериализация данных

Сериализация данных — это преобразование структурированных данных в формат, который позволяет делиться ими или сохранить, при этом вы можете воссоздать объект в памяти на получающей стороне (или при чтении из хранилища). В некоторых случаях еще одной причиной сериализации данных является минимизация сериализованных данных, что в свою очередь минимизирует занятое дисковое пространство или требования к полосе пропускания.

В следующих разделах рассматриваются формат Pickle, характерный для Python, некоторые инструменты сериализации между языками, способы сжатия, предлагаемые стандартной библиотекой Python, а также протокол буфера, который может снизить количество операций копирования данных перед их передачей.

Pickle

Нативный модуль сериализации данных для Python называется Pickle (). Рассмотрим пример его использования:

import pickle

# Пример словаря

grades = { 'Alice': 89, 'Bob': 72, 'Charles': 87 }

# Используем дампы для преобразования объекта в сериализованную строку

serial_grades = pickle.dumps( grades )

# Используем loads для десериализации строки в объект

received_grades = pickle.loads( serial_grades )

Функции, методы, классы и эфемерные объекты вроде конвейеров сериализовать нельзя.

14025.png

В соответствии с документацией к Pickle «модуль pickle небезопасно использовать для ошибочных или вредоносных данных. Никогда не десериализуйте данные, полученные из недостоверных источников».

Межъязыковая сериализация

Если вы ищете модуль сериализации данных, который поддерживает несколько языков, подойдут Protobuf от Google () и Avro от Apache ().

В стандартной библиотеке имеется библиотека xdrlib (), позволяющая упаковывать и распаковывать данные в формате External Data Representation (XDR) () от компании Sun. Этот формат не зависит от операционной системы и протокола передачи данных. Он работает на гораздо более низком уровне, нежели предыдущие варианты, и просто выполняет конкатенацию упакованных байтов, поэтому и клиент, и сервер должны знать тип и порядок упаковки. Рассмотрим пример сервера, получающего данные в формате XDR:

14037.png 

14044.png 

14048.png Данные могут иметь произвольную длину, поэтому мы добавили в начало файла упакованное беззнаковое число (4 байта), содержащее размер сообщения.

14059.png Мы должны знать заранее, что получаем данные типа unsigned int.

14071.png В этой строке считываем остальную часть сообщения…

14082.png …а в этой сбрасываем распаковщик, чтобы он начал работать с новыми данными.

14093.png Мы должны знать заранее, что получаем одну строку, а затем одно число с плавающей точкой.

Конечно, если обе стороны являлись программами Python, вы бы использовали Pickles. Но если сервер написан на каком-то другом языке, то соответствующий код клиента, отправляющего данные, выглядел бы так:

14108.png 

14118.png Сначала упакуем все данные, подлежащие отправке.

14126.png Далее отдельно упакуем длину сообщения…

14135.png …и добавим ее ко всему сообщению.

Сжатие

Стандартная библиотека Python поддерживает сжатие и декомпрессию данных с использованием алгоритмов zlib, gzip, bzip2 и lzma, а также создание архивов ZIP и TAR. Для того чтобы поместить в ZIP-архив данные, сериализованные с помощью Pickle, сделайте следующее:

import pickle

import gzip

data = "my very big object"

# Для запаковки и сериализации:

with gzip.open('spam.zip', 'wb') as my_zip:

    pickle.dump(data, my_zip)

# Для распаковки и десериализации:

with gzip.open('spam.zip', 'rb') as my_zip:

    unpickled_data = pickle.load(my_zip)

Протокол буфера

Элай Бендерски (Eli Bendersky), один из основных разработчиков Python, написал статью, посвященную вопросу снижения количества копий одних и тех же данных, хранящихся в памяти, с помощью буферов памяти (). Используя этот прием, вы даже можете считать данные из файла или сокета и поместить их в существующий буфер. Для получения более подробной информации обратитесь к документации для буферов протоколов и PEP 3118 (), где предлагаются улучшения, которые были реализованы в Python 3 и обратно портированы для версий Python 2.6 и выше.

Распределенные системы

Распределенные вычислительные системы выполняют задачу сообща (вроде игр, чат-комнат в Интернете или расчетов Hadoop) путем передачи информации друг другу.

В этом разделе сначала показываются самые популярные библиотеки для выполнения распространенных задач, связанных с сетью, а далее рассматривается шифрование (эта тема неотрывно следует за темой работы с сетью).

Работа с сетью

В Python коммуникация для соединенных сетей зачастую обрабатывается с помощью асинхронных инструментов или потоков, что позволяет обойти ограничение в один поток, создаваемое глобальной блокировкой интерпретатора. Все библиотеки, перечисленные в табл. 9.1, решают одну и ту же проблему — обходят GIL — с помощью разной функциональности.

Таблица 9.1. Работа с сетью

Библиотека

Лицензия

Причины использовать

asyncio

Лицензия PSF

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

Содержит асинхронные сокеты и очереди

gevent

Лицензия MIT

Тесно связана с libev — библиотекой для асинхронного ввода/вывода, написанной на С.

Предоставляет быстрый сервер WSGI, созданный на основе сервера HTTP.

Содержит полезный модуль gevent.monkey (), который имеет функции корректировки для стандартной библиотеки, что позволяет применять сторонние модули, использующие блокирующие сокеты

Twisted

Лицензия MIT

Предоставляет асинхронные реализации более новых протоколов, например GPS, Internet of Connected Products (IoCP), и протокола Memcached (/).

В ее цикл событий интегрированы разнообразные фреймворки, управляемые событиями вроде wxPython или GTK.

Имеет встроенный сервер SSH и клиентские инструменты

PyZMQ

Лицензии LGPL (ZMQ) и BSD (часть с Python)

Позволяет настроить неблокирующие очереди сообщений, использующие API, похожий на сокеты, а также взаимодействовать с ними.

Предоставляет поведения сокетов (запрос/ответ, публикация/подписка и отправка/получение), которые поддерживают распределенные вычисления.

Используйте эту библиотеку, если хотите создать собственную инфраструктуру для коммуникации; в ее имени содержится буква Q, но она не похожа на RabbitMQ — ее можно использовать для того, чтобы создать что-то вроде RabbitMQ или что-то, что имеет совершенно другое поведение (в зависимости от выбранных шаблонов сокетов)

pika

Лицензия BSD

Предоставляет легковесный клиент AMQP (протокол коммуникации) для соединения с RabbitMQ или другими брокерами сообщений.

Включает в себя адаптеры, подходящие для использования в циклах событий Tornado или Twisted.

Используйте ее вместе с брокером сообщений вроде RabbitMQ, если вам нужна более легковесная библиотека (без информационных панелей и других свистелок), которая позволяет отправлять содержимое внешнему брокеру сообщений вроде RabbitMQ

Celery

Лицензия BSD

Предоставляет клиент AMQP для соединения с RabbitMQ или другими брокерами сообщений.

Дает возможность сохранять состояния задач в бэкенде, который может использовать другие популярные варианты вроде соединения с базой данных с помощью SQLAlchemy, Memcached или другим способом.

Имеет необязательный к использованию инструмент для веб-администрирования и наблюдения, который называется Flower.

Может быть использована вместе с брокером сообщений вроде RabbitMQ в качестве моментально готовой к использованию системы-брокера сообщений

Производительность сетевых инструментов из стандартной библиотеки Python

Инструмент asyncio () был представлен в Python 3.4. Включает в себя идеи, почерпнутые у сообществ разработчиков вроде тех, что поддерживают библиотеки Twisted и gevent. Это инструмент для работы с конкуренцией, а самым частым приложением конкуренции являются сетевые сервера. В документации к asyncore (предшественнике asyncio) говорится следующее:

Существует лишь два способа заставить программу, работающую на одном процессоре, выполнять «больше одной задачи одновременно». Многопоточное программирование — самый простой и популярный способ сделать это, но существует еще один прием, который позволяет воспользоваться практически всеми преимуществами многопоточности, не задействуя на самом деле более одного потока. Применять этот прием имеет смысл, только если ваша программа ограничена по вводу/выводу. Если программа ограничена по процессору, то заранее запланированные потоки — это, возможно, именно то, что вам нужно. Однако сетевые сервера редко бывают ограниченными по процессору.

asyncio все еще находится в стандартной библиотеке Python на временной основе — ее API может измениться и потерять обратную совместимость, поэтому сильно не привыкайте.

Не вся функциональность нова — asyncore (объявлена устаревшей в Python 3.4) имеет цикл событий, асинхронные сокеты и асинхронный ввод/вывод информации из файлов, а asynchat (также объявлена устаревшей в Python 3.4) имеет асинхронные очереди. В asyncio добавлен один важный элемент — формализованная реализация сопрограмм. В Python это формально определяется как функция сопрограммы, то есть функция, чье описание начинается с конструкции async def, а не просто с def (если используется старый синтаксис, то применяется декоратор @asyncio.co­routine), и как объект, получаемый путем вызова функции сопрограммы (некого рода вычислений или операций ввода/вывода). Сопрограмма может обращаться к процессору и получить возможность участвовать в асинхронном цикле событий по очереди вместе с другими сопрограммами.

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

>>> import asyncio

>>>

>>> [l for l in asyncio.__all__ if 'loop' in l]

['get_event_loop_policy', 'set_event_loop_policy',

'get_event_loop', 'set_event_loop', 'new_event_loop']

>>>

>>> [t for t in asyncio.__all__ if t.endswith('Transport')]

['BaseTransport', 'ReadTransport', 'WriteTransport', 'Transport',

'DatagramTransport', 'SubprocessTransport']

>>>

>>> [p for p in asyncio.__all__ if p.endswith('Protocol')]

['BaseProtocol', 'Protocol', 'DatagramProtocol',

'SubprocessProtocol', 'StreamReaderProtocol']

>>>

>>> [q for q in asyncio.__all__ if 'Queue' in q]

['Queue', 'PriorityQueue', 'LifoQueue', 'JoinableQueue',

'QueueFull', 'QueueEmpty']

gevent

gevent (/) — это библиотека Python для работы с сетью, основанная на сопрограммах. Использует гринлеты, чтобы предоставить высокоуровневый синхронный API на базе цикла событий библиотеки libev (), написанной на С. Гринлеты основаны на библиотеке greenlet (/) — миниатюрные зеленые потоки () (или потоки уровня пользователя, по смыслу противоположные потоками, управляемым ядром), которые разработчик может свободно заморозить, переключаясь между гринлетами. Если хотите получить более подробную информацию, обратите внимание на семинар Кавьи Джоши (Kavya Joshi) A Tale of Concurrency Through Creativity in Python ().

Многие пользуются gevent, поскольку она легковесна и тесно связана с лежащей в ее основе библиотекой libev, написанной на С, что повышает производительность. Если вам нравится идея интеграции асинхронного ввода/вывода и гринлетов, эта библиотека отлично вам подойдет. Установите ее с помощью pip:

$ pip install gevent

Рассмотрим пример из документации к greenlet:

>>> import gevent

>>>

>>> from gevent import socket

>>> urls = ['', '', '']

>>> jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]

>>> gevent.joinall(jobs, timeout=2)

>>> [job.value for job in jobs]

['74.125.79.106', '208.77.188.166', '82.94.164.162']

В документации содержится множество других примеров.

Twisted

Twisted (/) — это управляемый событиями движок для работы с сетями. Он может применяться для создания приложений на основе разных сетевых протоколов, включая серверы и клиенты HTTP, а также приложений, использующих протоколы SMTP, POP3, IMAP или SSH, протоколы мгновенного обмена сообщениями, и многих других (). Установите его с помощью команды pip:

$ pip install twisted

Twisted существует с 2002 года и имеет верное сообщество. Ее можно назвать Emacs среди библиотек сопрограмм: все функции встроены (поскольку функциональность должна быть асинхронной для того, чтобы вы могли работать). Возможно, наиболее полезными инструментами являются асинхронная оболочка для соединений с базой данных (расположен в twisted.enterprise.adbapi), DNS-сервер (в twis­ted.names), прямой доступ к пакетам (в twisted.pair) и дополнительные протоколы вроде AMP, GPS и SOCKSv4 (в twisted.protocols). Большая часть функциональности Twisted работает и в Python 3. Когда вы вызываете команду pip install в среде Python 3, вы получаете все библиотеки, портированные к этому моменту. Если вы нашли то, что вам нужно, в API (), которого нет в вашей версии Twisted, вам стоит воспользоваться Python 2.7.

Для получения более подробной информации см. книгу Twisted (издательство O’Reilly) Джессики МакКеллар (Jessica McKellar) и Эйба Феттига (Abe Fettig). В дополнение к ней по адресу / приводится более 42 примеров использования Twisted, а в этом показываются их недавние достижения в скорости (/).

PyZMQ

PyZMQ (/) — это привязка к Python для ZeroMQ (/). Вы можете установить ее с помощью команды pip:

$ pip install pyzmq

ØMQ (также записывается как ZeroMQ, 0MQ или ZMQ) — библиотека для обмена сообщениями, которая имеет API, похожий на API сокетов. Предназначена для использования в масштабируемых распределенных или одновременно выполня­емых приложениях. По сути, она реализует асинхронные сокеты и очереди, а также предоставляет пользовательский список «типов» сокетов, которые определяют, как работает ввод/вывод для каждого сокета. Рассмотрим пример:

14170.png 

14178.png Тип сокета zmq.REP соответствует ее парадигме «запрос-ответ».

14187.png Как и в случае обычных сокетов, вы привязываете сервер к IP-адресу и порту.

14196.png Клиент имеет тип zmq.REQ — ZMQ определяет следующие константы: zmq.REQ, zmq.REP, zmq.PUB, zmq.SUB, zmq.PUSH, zmq.PULL, zmq.PAIR. Они устанавливают порядок отправки и принятия данных сокетом.

14207.png Как и обычно, клиент соединяется с IP-адресом и портом, привязанным к серверу.

Эта реализация выглядит и «крякает» как сокеты, улучшенные с помощью добавления очередей и разнообразных шаблонов ввода/вывода.

Идея использования шаблонов заключается в том, чтобы предоставить строительный материал для распределенной сети.

Сокеты имеют следующие основные шаблоны.

Запрос — ответ. zmq.REQ и zmq.REP соединяют набор клиентов с набором сервисов. Это может использоваться для создания шаблонов удаленного вызова процедуры или распределения задач.

• Публикация — подписка. zmq.PUB и zmq.SUB соединяют набор публикаторов с набором подписчиков. Этот шаблон предназначен для распространения данных (один узел распространяет данные другим), также с его помощью можно создать дерево распределения.

Отправка — получение (или конвейер). zmq.PUSH и zmq.PULL соединяют узлы с помощью шаблона разветвления на входе и выходе, который может иметь несколько шагов, а также циклы. С помощью этого шаблона реализуется параллельное распределение и сбор задач.

Одно из основных преимуществ ZeroMQ перед ориентированным на работу с сообщениями промежуточным ПО — библиотеку можно использовать для размещения сообщений в очереди без привлечения выделенного брокера сообщений. В документации к PyZMQ (/) указаны новые возможности вроде туннелирования с помощью SSH. Остальную часть документации к ZeroMQ API лучше искать в основном руководстве к ZeroMQ ().

RabbitMQ

RabbitMQ (/) — это брокер сообщений с открытым исходным кодом, реализующий протокол Advanced Message Queuing Protocol (AMQP). Брокер сообщений — промежуточная программа, которая получает сообщения с отправляющей стороны и пересылает их получателям в соответствии с протоколом. Любой клиент, который реализует AMQP, может связываться с RabbitMQ. Для того чтобы получить RabbitMQ, перейдите на его страницу загрузки () и следуйте инструкциям для вашей операционной системы.

Клиентские библиотеки, взаимодействующие с брокером, доступны для всех крупных языков программирования. Основными двумя библиотеками для Python являются pika и Celery — их можно установить с помощью pip:

$ pip install pika

$ pip install celery

pika (). Это легковесный клиент AMQP 0-9-1, написанный на чистом Python. Считается предпочтительным для RabbitMQ. В руководствах по RabbitMQ () используется именно pika. Кроме того, целая страница посвящена примерам работы с pika. Мы рекомендуем поработать с pika, как только вы установите RabbitMQ, независимо от того, какую библио­теку вы в итоге решите использовать, поскольку она довольно прямолинейна без дополнительной функциональности (это делает ее концепт довольно понятным).

Celery (). Это клиент AMQP, который имеет гораздо больше функциональности: может использовать в качестве брокера сообщений RabbitMQ или Redis (распределенное хранилище данных в оперативной памяти), а также отслеживать задачи и результаты (и опционально сохранять их в выбранном пользователем бэкенде). Этот клиент имеет инструмент Flower () для веб-администрирования/наблюдения за задачами. Он популярен среди веб-разработчиков. Существуют интеграционные пакеты для Django, Pyramid, Pylons, web2py и Tornado (для Flask такой пакет не нужен). Начинать работу лучше с руководства к Celery ().

Шифрование

В 2013 году сформировалась Python Cryptographic Authority (PyCA) () — группа разработчиков, заинтересованных в создании высококачественных библиотек для шифрования. Они предоставляют инструменты для шифрования и дешифрования сообщений на основе соответствующих ключей, а также криптографические хэш-функции, предназначенные для необратимого, но постоянного обфусцирования паролей или других секретных данных.

За исключением pyCrypto все библиотеки, представленные в табл. 9.2, поддерживаются PyCA. Практически все они созданы на основе библиотеки OpenSSL (/), написанной на С (кроме тех, где это указано).

Таблица 9.2. Реализация шифрования

Реализация

Лицензия

Причины использовать

ssl и hashlib (а в Python 3.6 еще и secrets)

Лицензия Python Software Foundation

Hashlib предоставляет неплохой алгоритм хэширования паролей, обновляемый по мере выхода новых версий Python, а ssl предоставляет клиент SSL/TLS (и сервер, но для него, возможно, обновления будут не самыми свежими).

Secrets — это генератор случайных чисел. Подходит для использования в криптографических целях

pyOpenSSL

Лицензия Apache v2.0

Использует самую последнюю версию OpenSSL в Python и предоставляет функции OpenSSL, недоступные в модуле стандартной библиотеки ssl

PyNaCl

Лицензия Apache v2.0

Содержит привязки к Python для libsodium*

libnacl

Лицензия Apache

Представляет собой интерфейс Python  для libsodium для тех, кто использует стек Salt (/)

cryptography

Лицензия Apache v2.0 или BSD

Предоставляет прямой доступ к криптографическим примитивам, созданным на основе OpenSSL. Большинство пользователей предпочитают более высокоуровневый pyOpenSSL

pyCrypto

Открытый доступ

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

bcrypt

Лицензия Apache v2.0

Предоставляет хэш-функцию bcrypt**. Полезна для тех, кто хочет использовать py-bcrypt (или работал с ней ранее)

* libsodium (/) — это версия библиотеки Networking and Cryptography (NaCl, произносится salt — соль); ее философия заключается в том, чтобы следовать определенным алгоритмам, которые имеют высокую производительность и которые легко использовать.

** Библиотека на самом деле содержит исходный код, написанный на С, и выполняет его сборку во время установки с помощью интерфейса C Fast Function, который мы описывали ранее. Bcrypt () основан на алгоритме шифрования Blowfish.

В следующих разделах приведена дополнительная информация о библиотеках из табл. 9.2.

ssl, hashlib и secrets

Модуль ssl () стандартной библиотеки Python предоставляет API для сокетов (ssl.socket), который ведет себя как обычный сокет, обернутый в протокол SSL, а также ssl.SSLContext, содержащий конфигурации для соединения по SSL. (или в Python 2) использует его для поддержки HTTPS. Если вы работаете с Python 3.5, можете задействовать memory BIO () — сокет будет записывать входную/выходную информацию в буфер, а не в место назначения, что позволяет, например, кодировать/декодировать данные в шестнадцатеричный формат перед записью или при чтении.

Основная часть улучшений безопасности появилась в Python 3.4 — вы можете узнать больше из заметок о выпуске () (появилась поддержка транспортных протоколов и алгоритмов хэширования). Это оказалось настолько важно, что функциональность была портирована в Python 2.7 (описывается в PEP 466 и PEP 476). Вы можете узнать больше сведений из речи Бенджамина Питерсона (Benjamin Peterson) о состоянии ssl в Python ().

14234.png

Если вы работаете с Python 2.7, убедитесь, что у вас установлена версия 2.7.9 или выше или что для вашей версии используется PEP 476, — по умолчанию клиенты HTTP будут выполнять проверку сертификата при соединении с помощью протокола . Либо всегда используйте библиотеку Requests (является вариантом по умолчанию).

Команда разработчиков Python рекомендует использовать значения по умолчанию для SSL, если ваша политика безопасности не предъявляет особых требований к клиенту. В этом примере показывается безопасный почтовый клиент (вы можете найти этот код в документации в библиотеке ssl по адресу (раздел Security considerations)):

>>> import ssl, smtplib

>>> smtp = smtplib.SMTP("mail.python.org", port=587)

>>> context = ssl.create_default_context()

>>> smtp.starttls(context=context)

(220, b'2.0.0 Ready to start TLS')

Для того чтобы убедиться в том, что сообщение не было повреждено во время передачи, используйте модуль hmac, который реализует алгоритм Keyed-Hashing for Message Authentication (HMAC), описанный RFC 2104 (). Он работает с сообщениями, хэшированными с помощью любого алгоритма из множества hashlib.algorithms_available. Для получения более подробной информации обратитесь к примеру из статьи Python Module of the Week (/). Если модуль у вас установлен, метод hmac.compare_digest() позволяет выполнять любые криптографические алгоритмы за константное время, чтобы защититься от атак по времени (атакующая сторона пытается определить ваш алгоритм шифрования на основе времени, которое требуется для выполнения криптографических алгоритмов).

Модуль hashlib может использоваться при генерации хэшированных паролей для безопасного хранилища или контрольных сумм с целью подтверждения сохранности данных во время передачи. Функция Password-Based Key Derivation Function 2 (PBKDF2), рекомендованная в NIST Special Publication 800-132 (), в данный момент считается лучшим способом хэширования пароля. Рассмотрим пример использования этой функции вместе с salt. При генерации хэшированного пароля используется 10 000 итераций алгоритма Secure Hash Algorithm для 256-битного хэша (SHA-256) (доступные алгоритмы хэширования и переменное количество итераций позволяют программисту сбалансировать устойчивость и желаемую скорость ответа):

import os

import hashlib

def hash_password(password, salt_len=16, iterations=10000, encoding='utf-8'):

    salt = os.urandom(salt_len)

    hashed_password = hashlib.pbkdf2_hmac(

        hash_name='sha256',

        password=bytes(password, encoding),

        salt=salt,

        iterations=iterations

    )

    return salt, iterations, hashed_password

Библиотека secrets () была предложена в PEP 506 (/), она доступна с версии Python 3.6. Предоставляет функции генерации токенов для безопасности, которые подходят приложениям, а также функции восстановления пароля и создания URL, которые сложно угадать. Ее документация содержит примеры и рекомендации по управлению безопасностью на базовом уровне.

pyOpenSSL

Когда вышла библиотека Cryptography, pyOpenSSL (/) обновила свои привязки так, чтобы использовать основанные на CFFI привязки Cryptography для OpenSSL и попасть под крыло PyCA.

pyOpenSSL намеренно не является частью стандартной библиотеки Python, чтобы можно было выпускать обновления безопасности с желаемой скоростью — ее строят для новых версий OpenSSL, а не для версий OpenSSL, поставляющихся с вашей операционной системой (если только вы не строите ее сами для новой версии). Как правило, если вы строите сервер, то используете pyOpenSSL (обратитесь к документации для SSL от Twisted по адресу  — там вы найдете пример применения pyOpenSSL).

Установите ее с помощью pip:

$ pip install pyOpenSSL

И импортируйте под именем OpenSSL. В этом примере показываются несколько доступных функций:

>>> import OpenSSL

>>>

>>> OpenSSL.crypto.get_elliptic_curve('Oakley-EC2N-3')

<Curve 'Oakley-EC2N-3'>

>>>

>>> OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)

<OpenSSL.SSL.Context object at 0x10d778ef0>

Команда разработчиков pyOpenSSL поддерживает код примера (), который включает в себя генерацию сертификатов, способ начать использовать SSL вместо уже соединенного сокета, а также безопасный сервер XMLRPC.

PyNaCl и libnacl

Идея, лежащая в основе libsodium () (библиотеки-бэкенда, написанной на C, для PyNaCl и libnacl), заключается в том, чтобы намеренно не давать пользователям выбор — лишь лучшие варианты из доступных в их ситуации. Она не поддерживает полностью протокол TLS; если вы хотите использовать этот протокол по максимуму, выбирайте pyOpenSSL. Если вам требуется лишь устанавливать зашифрованные соединения с другими компьютерами, которыми вы управляете, или с выбранными вами протоколами и вы не хотите работать с OpenSSL, эта библиотека отлично подойдет.

14253.png

PyNaCl произносится py-salt («пай-солт»), а libnacl — lib-salt («либ-солт»). Они обе созданы на основе библиотеки NaCl (salt) (/).

Мы рекомендуем использовать PyNaCl () вместо libnacl (/), поскольку за ней присматривает PyCA и вам не нужно отдельно устанавливать libsodium. Библиотеки, по сути, одинаковы: PyNaCl используют привязки CFFI для библиотек, написанных на C, а libnacl — ctypes (поэтому выбор библиотеки не имеет особого значения). Установите PyNaCl с помощью pip:

$ pip install PyNaCl

В докуменатции к PyNaCl по адресу / есть и примеры.

Cryptography

Cryptography (/) предоставляет рецепты и примитивы для шифрования. Поддерживает Python версий 2.6–2.7 и 3.3+, а также PyPy. PyCA рекомендует в большинстве случаев пользоваться высокоуровневым интерфейсом pyOpenSSL.

Cryptography состоит из двух уровней: рецептов и опасных материалов (hazardous materials, hazmat). Уровень рецептов предоставляет простой API для выполнения качественного симметричного шифрования, а уровень hazmat — низкоуровневые криптографические примитивы. Установите ее с помощью pip:

$ pip install cryptography

В этом примере используется высокоуровневый рецепт симметричного шифрования — единственная высокоуровневая функция этой библиотеки:

  from cryptography.fernet import Fernet

  key = Fernet.generate_key()

  cipher_suite = Fernet(key)

  cipher_text = cipher_suite.encrypt(b"A really secret message.")

  plain_text = cipher_suite.decrypt(cipher_text)

PyCrypto

PyCrypto (/) предоставляет безопасные хэш-функции, а также разнообразные алгоритмы шифрования. Поддерживает версии Python 2.1+ и Python 3+. Поскольку код, написанный на C, является пользовательским, PyCA осторожно работайте с библиотекой (но она использовалась де-факто для решения задач, связанных с шифрованием, многие годы, поэтому вы можете встретить ее в более старом коде). Установите ее с помощью pip:

$ pip install pycrypto

Использовать ее можно так:

from Crypto.Cipher import AES

# Шифрование

encryption_suite = AES.new('This is a key123', AES.MODE_CBC,

                  'This is an IV456')

cipher_text = encryption_suite.encrypt("A really secret message.")

# Дешифрование

decryption_suite = AES.new('This is a key123', AES.MODE_CBC,

                  'This is an IV456')

plain_text = decryption_suite.decrypt(cipher_text)

bcrypt

Если вы хотите применять алгоритм bcrypt () для ваших паролей, задействуйте эту библиотеку. Тем, кто раньше пользовался py-bcrypt, должно быть нетрудно перейти на нее, поскольку библиотеки совместимы. Установите ее с помощью pip:

pip install bcrypt

Она имеет всего две функции: bcrypt.hashpw() и bcrypt.gensalt(). Последняя позволяет выбирать количество итераций — чем больше итераций, тем медленнее работает алгоритм (по умолчанию задается их разумное количество). Рассмотрим пример:

>>> import bcrypt

>>>>

>>> password = bytes('password', 'utf-8')

>>> hashed_pw = bcrypt.hashpw(password, bcrypt.gensalt(14))

>>> hashed_pw

b'$2b$14$qAmVOCfEmHeC8Wd5BoF1W.7ny9M7CSZpOR5WPvdKFXDbkkX8rGJ.e'

Сохраняем хэшированный пароль:

>>> import binascii

>>> hexed_hashed_pw = binascii.hexlify(hashed_pw)

>>> store_password(user_id=42, password=hexed_hashed_pw)

Когда приходит время проверять пароль, используйте хэшированный пароль в качестве второго аргумента функции bcrypt.hashpw() следующим образом:

>>> hexed_hashed_pw = retieve_password(user_id=42)

>>> hashed_pw = binascii.unhexlify(hexed_hashed_pw)

>>>

>>> bcrypt.hashpw(password, hashed_pw)

b'$2b$14$qAmVOCfEmHeC8Wd5BoF1W.7ny9M7CSZpOR5WPvdKFXDbkkX8rGJ.e'

>>>

>>> bcrypt.hashpw(password, hashed_pw) == hashed_pw

True

Стек протоколов TCP/IP (или Internet Protocol) имеет четыре концептуальные части: протоколы канального уровня указывают, как получить информацию от компьютера и Интернета. За их работу отвечают сетевые карты и операционные системы, но не программы Python. Протоколы сетевого уровня (IPv4, IPv6 и т. д.) управляют доставкой пакетов, состоящих из битов, от источника к месту назначения — стандартные варианты предоставлены в библиотеке сокетов для Python (). Протоколы транспортного уровня (TCP, UDP и т.д.) указывают, как будут общаться две конечные точки. Возможные варианты также находятся в библиотеке сокетов. Наконец, протоколы прикладного уровня (FTP, HTTP и т.д.) указывают, как должны выглядеть данные для того, чтобы их могло использовать приложение (например, FTP применяется для передачи файлов, а HTTP — для передачи гипертекста) — в стандартной библиотеке Python предоставляются отдельные модули, реализующие наиболее распространенные протоколы.

Сокет состоит из трех элементов: IP-адреса, включая номер порта, транспортного протокола (вроде TCP/UDP) и канала ввода/вывода (объект, похожий на файл). В документации к Python приводится отличное введение в тему сокетов ().

Для очереди не требуются IP-адрес или протокол, поскольку она реализуется на одном компьютере; вы просто записываете в нее данные — и другой процесс может их прочитать. Очередь похожа на multiprocessing.Queue, но здесь операции ввода/вывода выполняются асинхронно.

Рождение библиотеки cryptography, а также история ее создания описаны в статье Джейка Эджа (Jake Edge) The state of crypto in Python (). cryptography — это низкоуровневая библиотека, предназначенная для импортирования более высокоуровневыми библиотеками вроде pyOpenSSL. Эдж цитирует беседу Джаррета Рэйма (Jarret Raim) и Пола Керера (Paul Kehrer), посвященную State of Crypto in Python (), и утверждает, что их набор тестов состоит более чем из 66 000 элементов, которые запускаются 77 раз при каждой сборке.

salt — это случайная строка, которая еще больше обфусцирует хэш; если бы все использовали один и тот же алгоритм, nefarious actor смог бы сгенерировать таблицу, содержащую распространенные пароли и их хэши, и применить их для «декодирования» украденных файлов с паролями. Чтобы это предотвратить, к паролю прикрепляется случайная строка (salt), так что ее придется хранить для использования в будущем.

Любой человек может подписаться на рассылку cryptography-dev от PyCA, чтобы быть в курсе последних разработок и других новостей. Существует также рассылка новостей OpenSSL.

Если вы хотите полностью управлять вашим кодом, отвечающим за шифрование, и вам неважно, что он работает чуть медленнее, и не нужны самые свежие алгоритмы, попробуйте библиотеку TweetNaCl (/), которая состоит из одного файла и помещается в сотню твитов. Поскольку релиз PyNaCl поставляется вместе с libsodium, вы, скорее всего, можете использовать TweetNaCl и в то же время запускать практически все (правда, мы сами не пробовали так делать).

Назад: 8. Управление кодом и его улучшение
Дальше: 10. Манипуляции с данными