Full-stack Web Developer
0,0
рейтинг
15 ноября 2015 в 13:59

Разработка → Асинхронный Python 3.5 и Mongodb перевод tutorial


Это достаточно вольный перевод статьи об основных новшествах асинхронного драйвера для mongodb используемого в tornado. Основной мотив, который послужил для написания этого перевода — новшества, появившиеся в этой версии, такие как поддержка asyncio, async, await и Python 3.5. Сама статья не сколько перечисление новшеств, сколько лаконичные примеры асинхронной работы с MongoDB.


Введение
asyncio
aggregate
Python 3.5
async and await


Введение


Недавно была опубликована новая Beta версия Python драйвера для Mongodb, Motor. В этой версии содержится одно из самых больших обновлений. Для установки можно использовать:
python -m pip install --pre motor==0.5b0

Motor 0.5 по прежнему зависит от PyMongo 2.8.0. Это устаревшая версия PyMongo, но сейчас не было достаточно времени чтоб полностью перейти на третью версию, что простительно, так как этот релиз достаточно большой.

asyncio


Motor теперь может интегрироваться с asyncio, как альтернатива Tornado. Большая благодарность Реми Джолину, Андрею Светлову svetlov и Николаю Новику за их огромный вклад в интеграцию Motor для работы с asyncio.

API-Интерфейсы Tornado и asyncio являются родственными. Пример Motor с Tornado:
# Tornado API
from tornado import gen, ioloop
from motor.motor_tornado import MotorClient

@gen.coroutine
def f():
    result = yield client.db.collection.insert({'_id': 1})
    print(result)

client = MotorClient()
ioloop.IOLoop.current().run_sync(f)

И здесь пример для asyncio:
import asyncio
from motor.motor_asyncio import AsyncIOMotorClient

@asyncio.coroutine
def f():
    result = yield from client.db.collection.insert({'_id': 1})
    print(result)

client = AsyncIOMotorClient()
asyncio.get_event_loop().run_until_complete(f())

В отличие от Tornado, asyncio не включает реализацию http, а тем более не является фреймворком. Для этого используйте библиотеку aiohttp Андрея Светлова. Небольшой пример для работы Motor с aiohttp.

aggregate


MotorCollection.aggregate теперь по умолчанию возвращает курсор, и курсор возвращается непосредственно без yield. Старый синтаксис больше не поддерживается:
# Motor 0.4 and older, no longer supported.
cursor = yield collection.aggregate(pipeline, cursor={})
while (yield cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

В Motor 0.5 просто сделайте:
# Motor 0.5: no "cursor={}", no "yield".
cursor = collection.aggregate(pipeline)
while (yield cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

В asyncio для этого используется yield from:
# Motor 0.5 with asyncio.
cursor = collection.aggregate(pipeline)
while (yield from cursor.fetch_next):
    doc = cursor.next_object()
    print(doc)

Python 3.5


Сейчас Motor совместим с Python 3.5, что потребовало определённых усилий. Это было трудно, потому что Motor не просто работает с сопрограммами (coroutines), он использует сопрограммы внутри себя для реализации некоторых из своих функций, таких как MotorClient.open и MotorGridFS.put.
Был метод для написания сопрограмм, которые работают в Python 2.6 c Python 3.4, но в Python 3.5 это было окончательно поломано. Нет единого пути для возвращения значений из Python 3.5 нативной сопрограмме или Python 2 генератора базирующегося на сопрограмме, так что все внутренние сопрограммы motor, которые возвращают значения, были переписаны с помощью обратных вызовов.

async and await


Награда за усилия потраченные на интеграцию с Python 3.5, состоит в том что теперь motor работает с родной сопрограммой, написанной с учетом ключевых слов async и await синтаксис:
async def f():
    await collection.insert({'_id': 1})

Курсор из MotorCollection.find, MotorCollection.aggregate, или MotorGridFS.find может быть красиво и очень эффективно итегрирован в нативных сопрограммах (coroutines) с async for:
async def f():
    async for doc in collection.find():
        print(doc)

Насколько эффективно? Для коллекции из 10 000 документов этот пример кода выполнялся за 0.14 секунды.
# Motor 0.5 with Tornado.
@gen.coroutine
def f():
    cursor = collection.find()
    while (yield cursor.fetch_next):
        doc = cursor.next_object()
        print(doc)


Следующий код, в котором просто заменены gen.coroutine и yield на async и await и выполняет примерно тоже.
# Motor 0.5 with Tornado, using async and await.
async def f():
    cursor = collection.find()
    while (await cursor.fetch_next):
        doc = cursor.next_object()
        print(doc)

Но с async for время работы занимает 0.04 секунды, то есть в три раза быстрее.
# Motor 0.5 with Tornado, using async for.
async def f():
    cursor = collection.find()
    async for doc in cursor:
        print(doc)

Однако, MotorCursor в to_list прежнему играет основную роль:
# Motor 0.5 with Tornado, using to_list.
async def f():
    cursor = collection.find()
    docs = await cursor.to_list(length=100)
    while docs:
        for doc in docs:
            print(doc)
        docs = await cursor.to_list(length=100)

Функция с to_list в два раза быстрее чем асинхронные, но это выглядит не так красиво и требует указания размера чанка. Я думаю, что async for выглядит довольно стильно и работает достаточно быстро для того, чтобы его применять в большинстве случаев.

Бета версии релизов motor публиковались далеко не всегда, но в этот раз по-другому. Интеграция asyncio в motor является совершенно новой. И поскольку это потребовало повсеместного рефакторинга ядра motor, и переписывания существующей интеграции tornado, была выпущена бета-версия для того, чтобы исправить все упущения.

P.S. Просьба о грамматических ошибках и ошибках перевода писать в личку.
Перевод: A. Jesse Jiryu Davis
Александр @Alex10
карма
73,0
рейтинг 0,0
Full-stack Web Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (22)

  • 0
    сколько миллисекунд в секунде?)
    • +1
      точно, надо поправить на сантисекунды
  • 0
    del
  • 0
    Мотор до сих пор не умеет третью монгу и «async for». Печаль…

    Upd: прочитал пост. Когда я смотрел, «async for» не умел.
    • 0
      Уточню, не умеет с PyMongo3 с Mongodb3 вроде PyMongo2.8 работал.
      С PyMongo3 подружить Motor обещают к весне.
      • +1
        Нет, pymongo2 с mognodb3 работет очень странно и не всегда предстказуемо. Например, любит тупо виснуть, блокируя приложение.
        Так что надо апгрейдить мотор. Если никто не займётся, через месяцок займусь сам…
        • 0
          Ну ближайший месяц точно никто не займется, ближайший месяц разве что motor0.5 стабильную версию выпустят.
    • –1
      А как вы смотрите на то, что он пытается «замесить» asyncio с greenlet?
      • +1
        Отрицательно. Не вижу ни малейшего практического смысла мешать ежа с ужом.
        Если он есть, то буду рад про него узнать… :-)
        • 0
          И я так считаю, надо будет спросить автора Motor.
          Наверно он использует гринлеты, что-бы не переписывать весь pymongo.
          • +1
            Я, если честно, не копал мотор так глубоко. Количество магии там за гранью моего понимания… Но в любом случае, это сейчас самая полная и стабильная библиотека для асинхронной работы с монгой. Пробовал оценить, сколько времени надо на то, чтобы написать свою и что-то у меня много выходило.
  • 0
    Сам работаю с Python, но каждый раз, когда вижу различные асинхронные штуки на нём, в голове невольно возникает фраза «Тулить горбатого к стене».
    • 0
      Вполне возможно, но многим все таки это помогает, мне например очень понравился aiohttp написанный поверх asyncio, это очень удобно когда фреймворк умеет примерно все то же самое что flask но с вебсокетами и причем использовать их с ним очень удобно.

      Вот к примеру, сегодня буквально попалось, тем кто работает с blender тоже asyncio пригодился.
      • +2
        Согласен. Очень радует, что язык растёт и развивается. Возможно, в версии 4 мы сможем увидеть и более красивый, «причесанный» вариант работы с асинхронностью.
  • 0
    Ну замечательно. Что всё таки дает asyncio против Tornado?
    • 0
      Где-то видел слайды с доклада, в которых разработчик уверял, что добился почти такой же производительности, как в tornado. Только без подключаемых библиотек на си. Но не берусь утверждать — сам не проверял.
    • +1
      Поддержка не от разработчиков Торнадо, а от сообщества Python. Ну и вроде фишек в нём по больше будет. Собственно Торнадо умеет работать с asyncio.
      • 0
        Мг, а это хорошо, хорошо.
    • +1
      Asyncio был создан как «стандарт», т.к. существующие асинхронные фреймворки (tornado, twisted, pulsar, cyclone.io...) как-бы «разрывают» питон на сегменты — сообщество тратят кучу ресурсов на дублирующие проекты, например — вместо того что-бы развивать один клиент MongoDB, разработчики пилят версию для tornado (asyncmongo) и отдельно версию для twisted (txmongo), можно было-бы вложится в один клиент для psql/websockets и др. но разработчики «вынуждены» пилить «клон» под свой фреймворк.

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

      Ну и естественно в этот «стандарт» asyncio, было вложено куча работы, что-бы покрывать множество запросов разных фреймворков.
  • –1
    А на оригинал статьи, с которой приводится вольный перевод давать не принято? Или я не вижу просто?
    • +1
      В топиках-переводах традиционно ссылка на оригинал и имя автора внизу статьи.
      • 0
        А, всё, действительно, не заметил. Спасибо.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.