Это достаточно вольный перевод статьи об основных новшествах асинхронного драйвера для 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. Просьба о грамматических ошибках и ошибках перевода писать в личку.

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


  1. mx2000
    15.11.2015 15:39

    сколько миллисекунд в секунде?)


    1. Yahweh
      15.11.2015 16:19
      +1

      точно, надо поправить на сантисекунды


  1. Yahweh
    15.11.2015 16:18

    del


  1. ZZZ_Sochi
    15.11.2015 18:29

    Мотор до сих пор не умеет третью монгу и «async for». Печаль…

    Upd: прочитал пост. Когда я смотрел, «async for» не умел.


    1. Alex10
      15.11.2015 18:42

      Уточню, не умеет с PyMongo3 с Mongodb3 вроде PyMongo2.8 работал.
      С PyMongo3 подружить Motor обещают к весне.


      1. ZZZ_Sochi
        15.11.2015 22:48
        +1

        Нет, pymongo2 с mognodb3 работет очень странно и не всегда предстказуемо. Например, любит тупо виснуть, блокируя приложение.
        Так что надо апгрейдить мотор. Если никто не займётся, через месяцок займусь сам…


        1. Alex10
          15.11.2015 22:58

          Ну ближайший месяц точно никто не займется, ближайший месяц разве что motor0.5 стабильную версию выпустят.


    1. lega
      15.11.2015 18:50
      -1

      А как вы смотрите на то, что он пытается «замесить» asyncio с greenlet?


      1. ZZZ_Sochi
        15.11.2015 23:16
        +1

        Отрицательно. Не вижу ни малейшего практического смысла мешать ежа с ужом.
        Если он есть, то буду рад про него узнать… :-)


        1. lega
          16.11.2015 00:22

          И я так считаю, надо будет спросить автора Motor.
          Наверно он использует гринлеты, что-бы не переписывать весь pymongo.


          1. ZZZ_Sochi
            16.11.2015 13:01
            +1

            Я, если честно, не копал мотор так глубоко. Количество магии там за гранью моего понимания… Но в любом случае, это сейчас самая полная и стабильная библиотека для асинхронной работы с монгой. Пробовал оценить, сколько времени надо на то, чтобы написать свою и что-то у меня много выходило.


  1. denis_g
    15.11.2015 18:57

    Сам работаю с Python, но каждый раз, когда вижу различные асинхронные штуки на нём, в голове невольно возникает фраза «Тулить горбатого к стене».


    1. Alex10
      15.11.2015 19:07

      Вполне возможно, но многим все таки это помогает, мне например очень понравился aiohttp написанный поверх asyncio, это очень удобно когда фреймворк умеет примерно все то же самое что flask но с вебсокетами и причем использовать их с ним очень удобно.

      Вот к примеру, сегодня буквально попалось, тем кто работает с blender тоже asyncio пригодился.


      1. denis_g
        15.11.2015 19:10
        +2

        Согласен. Очень радует, что язык растёт и развивается. Возможно, в версии 4 мы сможем увидеть и более красивый, «причесанный» вариант работы с асинхронностью.


  1. novoxudonoser
    15.11.2015 23:47

    Ну замечательно. Что всё таки дает asyncio против Tornado?


    1. bosha
      16.11.2015 00:17

      Где-то видел слайды с доклада, в которых разработчик уверял, что добился почти такой же производительности, как в tornado. Только без подключаемых библиотек на си. Но не берусь утверждать — сам не проверял.


    1. stalkerg
      17.11.2015 13:52
      +1

      Поддержка не от разработчиков Торнадо, а от сообщества Python. Ну и вроде фишек в нём по больше будет. Собственно Торнадо умеет работать с asyncio.


      1. novoxudonoser
        17.11.2015 14:59

        Мг, а это хорошо, хорошо.


    1. lega
      17.11.2015 15:29
      +1

      Asyncio был создан как «стандарт», т.к. существующие асинхронные фреймворки (tornado, twisted, pulsar, cyclone.io...) как-бы «разрывают» питон на сегменты — сообщество тратят кучу ресурсов на дублирующие проекты, например — вместо того что-бы развивать один клиент MongoDB, разработчики пилят версию для tornado (asyncmongo) и отдельно версию для twisted (txmongo), можно было-бы вложится в один клиент для psql/websockets и др. но разработчики «вынуждены» пилить «клон» под свой фреймворк.

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

      Ну и естественно в этот «стандарт» asyncio, было вложено куча работы, что-бы покрывать множество запросов разных фреймворков.


  1. shrimpsizemoose
    16.11.2015 02:57
    -1

    А на оригинал статьи, с которой приводится вольный перевод давать не принято? Или я не вижу просто?


    1. BlessMaster
      16.11.2015 03:17
      +1

      В топиках-переводах традиционно ссылка на оригинал и имя автора внизу статьи.


      1. shrimpsizemoose
        17.11.2015 18:04

        А, всё, действительно, не заметил. Спасибо.