Рубрики
Без рубрики

twisted_ipython: Autoaawait в ноутбуках Jupyter с помощью скрученными

Я написал расширение iPython, которое позволяет AUTOAVAIT с помощью скрученных Ciboutines – прочитайте, чтобы узнать, как!. Теги от Jupyter, Python, витой, асинхронизации.

Немного до года назад я написал Это расширение iPython Это позволяет AUTOWAIT для Врисованные отложенные в Jupyter ноутбуки Отказ

Но в прошлый четверг я посещал виртуальную ночь на молнии, размещенную NYC Python Meetup Group И, хотя я планировал просто охлаждать, я закончил разговаривать на этом проекте на основе слайды я сделал несколько месяцев назад И не смотрел в промежуточном состоянии. Он пошел Хорошо Отказ

Организаторы предположили, что я превращаю эти слайды в пост в блоге. Смешно достаточно, я на самом деле не считать это до сих пор – ведь проект сам был довольно хорошо документирован и на Github, верно? Но они, вероятно, верны, что больше людей прочитают сообщение в блоге, чем техническая документация. Кроме того, это возможность сделать глубокое погружение! Это, и они сказали, что это хороший способ заметить, если вы пытаетесь найти работу (рискую быть маленьким Greyfty: Найди меня ). Так что здесь мы идем!

Чтобы установить сцену, я хочу поговорить о трех вещах, я большой поклонник: скрученный, асинхн/ждут, а jupyter. Если вы уже знаете об этих вещах, не стесняйтесь пропустить впереди.

Витая (и грунтовка на асинхронном IO)

Прежде всего, Скрученный отлично.

Скрученные для тех незнакомых – это асинхронная сетевая библиотека для Python. Большинство сетей в Python блокировка – то есть, когда вы, например, сделаете веб-запрос с Запросы библиотеки , Python перестает делать, хорошо, все вообще, пока запрос не будет завершен. Это не совсем асинхронная библиотека дюжка в эти дни – скрученные были впервые выпущены в 2002 , Core Python теперь отправляется со своими собственными каркасами (называется Asyncio ) И в эти дни Новая жаркость считается Трио в пределах Сообщество Отказ Но это стояло испытание времени. С самого начала было очень хорошо архиваторно, получили последовательные обновления в течение многих лет, а на вершине этого есть отличный сопровождающие . Я отправил на них патч в прошлом году, и это было все вокруг положительного опыта.

Например, давайте возьмем PowerShell Project Mee Где я ищу Merriam-Webster TheSaurus API для Утвержденные глаголы Powershell И портируйте скрипт CLI к Python. В этом скрипте мы делаем вызов API к Функция Azure Ответ Microsoft на AWS LAMBDA Разберите ответ в качестве JSON и распечатайте результаты на экран:

#!/usr/bin/env python3

import sys
import requests

if not sys.argv[1]:
    raise Exception('Need a thing to search for!')

query = sys.argv[1]

# Hanging out, just doing Python things - in this case printing output

print(f'Searching the Verbtionary for {query}...')
print()

# Making a blocking web request - this is an Azure Function that doesn't get
# called very often so due to cold start it will take a few seconds to
# complete...

res = requests.get('https://verbtionary.azurewebsites.net/api/search?query=edit')

# OK, our request is done now - but notice that we didn't /do/ anything during
# that time other than making the web request. No printing, no multitasking,
# just... hanging out.

# But now that this is done, we can do really simple error handling and
# response parsing...

res.raise_for_status()
payload = res.json()

# ...and print out our results!

print('Verb\tAlias Prefix\tGroup\tDescription')
print('----\t------------\t-----\t-----------')
for result in payload['Body']:
    print('\t'.join([
        result['Verb'],
        result['AliasPrefix'],
        result['Group'],
        result['Description']
    ]))

На самом деле работает что-то подобное:

(base) [josh@starmie ~]$ ./verbtionary.py edit
Searching the Verbtionary for edit...

Verb    Alias Prefix    Group   Description
----    ------------    -----   -----------
Edit    ed  Data    Modifies existing data by adding or removing content
Send    sd  Communications  Delivers information to a destination
Stop    sp  Lifecycle   Discontinues an activity
Write   wr  Communications  Adds information to a target

В этом случае мое испытательное слово, редактировать, на самом деле является утвержденным глаголом. Здорово!

По умолчанию все IO в Python, будь то сетевые запросы, такие как эти, База данных вызовов или Обслуживание API , блокируются. Когда вы служите http Колбу Каждый процесс может обрабатывать только один запрос одновременно.

Это не так плохо, как звучит – в конце концов, есть много исполнительных API, написанных в колбе. Для WebApps люди, как правило, используют сервер WSGI, как uwsgi. или боевик которые обычно используют Предварительная модель Чтобы создать отдельный процесс для каждого запроса. Для создания нескольких вызовов API одновременно (обычно из-за проблем n + 1 ) Я часто добрался до ThreadPoolExecutors Отказ Общая нить 😏 здесь использует процессы или потоки для запуска нескольких блокирующих вещей параллельно.

Конечно, есть недостатки для обоих этих подходов. В случае процессов создание нового процесса требуется время! Очень очень повседневный ориентир показывает, что Python занимает 150-200 миллисекунд, чтобы не было много:

(base) [josh@starmie ~]$ time python -c ""

real    0m0.019s
user    0m0.018s
sys 0m0.001s

Нитки также имеют время запуска, хотя они более легкие, чем полный процесс. Тем не менее, Python также имеет то, что называется Блокировка глобального переводчика Это означает, что даже в лучшем случае сценарий только один поток может запустить фактический Python в любой момент времени. Если вы делаете сеть IO, это часто приемлемо, и если вы используете вычислительную библиотеку, как воплощение Это может приступить к проблеме, делая параллелизацию в C-Land. Кроме того, нитки могут быть сложными для рассуждения, потому что любые два потока имеют доступ к той же памяти и произвольно принимают и выпускают GIL. Из-за этого использование потоков требует использования абстракций, таких как Замки , очереди , Семафоры И если вам повезет, исполнители Отказ

Альтернатива к обоим этим Модели параллелизма это то, что называется в совокупности IO. Большинство операционных систем в наши дни имеют механизм мониторинга файловых дескрипторов и передача обновлений для них в качестве событий к данному процессу – для примеров, Epoll В Linux, kqueue в BSDS и IOCP в окнах. Событы IO Frameworks, такие как скрученные подключения к этим API, чтобы сделать их IO.

Почему это хорошая идея? С помощью этой системы данный процесс еще имеет только один переводчик Python и на самом деле только один нить, но это также бесплатно делать другие вещи, пока ОС управляет IO. Это означает, что параллелизм в процессе теперь несущественно возможен, все без накладных расходов нескольких процессов или потоки.

Например, это то, что этот скрипт будет выглядеть так, как использовал скрученный и Treq вместо запросов. Это будет выглядеть Лот Усложнее – и это! Но отдых уверен, что это Интерфейс низкого уровня И что мы будем показывать намного легче читать версию этого следующего:

#!/usr/bin/env python3

import sys

import treq

# Twisted uses a special object called a reactor to manage interacting with
# the OS's event system and runs what is called an event loop.
from twisted.internet import reactor

# We use this little helper to run our code and shut off the event loop
# when everything is done.
from twisted.internet.task import react

# When doing evented IO, we create callback functions, which get called by
# our framework when IO completes. Here we have two non-blocking IO calls -
# one to initiate a request, and one to pull down the body of the response.

# This function makes our initial request...
def makeRequest(query):
    print(f'Searching the Verbtionary for {query}...')
    print()

    # This method call returns an object called a Deferred - we'll come back
    # to how to use this later!
    return treq.get(
        'https://verbtionary.azurewebsites.net/api/search?query=edit'
    )



# ...this one does simple error handling and loads the JSON response...
def handleResponse(res):
    if res.code != 200:
        raise Exception(f'Status {res.code}!')

    # This *also* returns a Deferred - this is because we can handle the
    # status code before we even read in the response body!
    return res.json()



# ...and this one prints our results!
def printResults(payload):

    print('Verb\tAlias Prefix\tGroup\tDescription')
    print('----\t------------\t-----\t-----------')
    for result in payload['Body']:
        print('\t'.join([
            result['Verb'],
            result['AliasPrefix'],
            result['Group'],
            result['Description']
        ]))



# The Deferred objects allow us to register our callbacks with Twisted to be
# called when their associated IO is complete.
def main(reactor, *argv):
    if not argv[1]:
        raise Exception('Need a thing to search for!')

    query = sys.argv[1]

    return makeRequest(query).addCallback(handleResponse).addCallback(printResults)



# So here's the COOL PART: While all that is happening, we can now multitask!
# Here, I use the callLater API to print stuff
reactor.callLater(0.1, print, 'Hanging out!')
reactor.callLater(0.5, print, 'Partying hard!')

# Finally, we tell the reactor to run everything - it'll keep running until
# our API request is done and everything's printed to the screen.
react(main, sys.argv)

В этом коде мы импортируем реактор с витой – это то, что называется Структура событий Отказ Структура событий отвечает за отправку асинхронных запросов IO в ОС и обработку событий завершения, поскольку они происходят. Затем мы создаем кучу функций обратного вызова. Они известны как обратные вызовы, потому что контур событий вызывает их, когда асинхронные события должны быть обработаны. Эти функции делают все вещи, которые мы сделали в другом скрипте, но разбивайте его в куски, которые делают некоторые легкие синхронные вещи (например, печатать на экран или аналитик JSON), прежде чем пить некоторую асинхронитую IO и управление управлением обратно в реактор Отказ Наконец, мы проводят их вместе с Addcallback Метод . На специальном объекте под названием Отложить И запустите нашу задачу, используя Помощник скрученного реагирования Отказ

Это похоже на кучу Hullabaloo, но это позволяет что-то действительно круто: в то время как Treq называет мою функцию Super Slow Azure, мы можем сделать другие вещи! В этом случае я просто скажуте реактору называть функцию печати на 0,1 секунды в будущем и на 0,5 секунды в будущее, соответственно:

reactor.callLater(0.1, print, 'Hanging out!')
reactor.callLater(0.5, print, 'Partying hard!')

Вот наш новый скрипт в действии:

(korbenware) [josh@starmie ~]$ ./verbtionary-twisted.py edit
Searching the Verbtionary for edit...

Hanging out!
Partying hard!
Verb    Alias Prefix    Group   Description
----    ------------    -----   -----------
Edit    ed  Data    Modifies existing data by adding or removing content
Send    sd  Communications  Delivers information to a destination
Stop    sp  Lifecycle   Discontinues an activity
Write   wr  Communications  Adds information to a target

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

Конечно, печать на экран не очень полезна, но мы можем сделать все сортирует связанного с сетью вещей – мы могли бы сделать больше запросов или разместить веб-сервер или отправлять и получать электронные письма или даже IDLE IRC Отказ Есть много возможностей!

Как я уже упоминал ранее, это все выглядит супер сложным. К счастью, Python имеет специальные ключевые слова, которые могут быть использованы, чтобы сделать это намного проще, а именно async def и ждать (коллективно упоминается как async/await ). Эти специальные ключевые слова позволяют определить Coroutines , что можно рассматривать как специальные функции, которые «распаковывают» вещи Как Наши отсрочки в скрученном, называемом Очень ждет Отказ Эти ожидающие работы для многих асинхронных рамок, в том числе асинсио и Трио , но по состоянию на 2016 год они Также Работа с витой.

Пример должен сделать это более понятно. Вот наш код кода, чтобы использовать Async/ждут, и я думаю, что вы будете таким образом, как я:

#!/usr/bin/env python3

import sys

# This is just like we did before...
import treq
from twisted.internet import reactor
from twisted.internet.task import react

# To make this work, we have to import a handy function that runs coroutines
# for us called ensureDeferred, which takes a coroutine and returns a Deferred
# that we can then use just like before!
from twisted.internet.defer import ensureDeferred


# Now everything is in one tidy function! Note that where we
# needed callbacks before, we now simply use the "await" keyword.
# Much nicer!
async def searchVerbtionary(argv):
    if not argv[1]:
        raise Exception('Need a thing to search for!')

    query = sys.argv[1]

    print(f'Searching the Verbtionary for {query}...')
    print()

    res = await treq.get(
        'https://verbtionary.azurewebsites.net/api/search?query=edit'
    )

    if res.code != 200:
        raise Exception(f'Status {res.code}!')

    payload = await res.json()

    print('Verb\tAlias Prefix\tGroup\tDescription')
    print('----\t------------\t-----\t-----------')
    for result in payload['Body']:
        print('\t'.join([
            result['Verb'],
            result['AliasPrefix'],
            result['Group'],
            result['Description']
        ]))



def main(reactor, *argv):
    # This is where we use ensureDeferred - like in the previous
    # example, the react function needs to be handed a Deferred, not a
    # coroutine, so we make the conversion here.
    return ensureDeferred(searchVerbtionary(argv))


# We're still using the callLater API to do things asynchronously here!
reactor.callLater(0.1, print, 'Hanging out!')
reactor.callLater(0.5, print, 'Partying hard!')

react(main, sys.argv)

Намного лучше! – И если мы выпустим RIP:

(korbenware) [josh@starmie ~]$ ./verbtionary-asyncawait.py edit
Searching the Verbtionary for edit...

Hanging out!
Partying hard!
Verb    Alias Prefix    Group   Description
----    ------------    -----   -----------
Edit    ed  Data    Modifies existing data by adding or removing content
Send    sd  Communications  Delivers information to a destination
Stop    sp  Lifecycle   Discontinues an activity
Write   wr  Communications  Adds information to a target

Поведение точно так же, как и раньше, но теперь вместо того, чтобы вручную определить обратные вызовы и провести все отсроченные вместе с Addcallback API, мы можем написать весь наш код в аккуратном COROUTINE и запустить его, когда мы будем готовы. Async/a a await оказывается действительно удобным по этой причине, и я почти всегда хочу использовать их вместо отсроченных API напрямую.

Jupyter.

Jupyter – это то, что называется ноутбук , грамотная среда программирования, которая может занять место репре. Jupyter очень популярен в Наука данных пространство. Я часто использую ноутбуки, даже когда я не делаю науку. Например, я недавно Отредактировал видео Использование Кинопя И jupyter, и это выглядело так:

На этой картинке серая коробка называется «ячейкой», и вы можете редактировать код Python внутри него. Затем вы можете запустить его с Shift-Enter И это запустит код и, для библиотек, которые его поддерживают, вывод на богатстве, в этом примере встроенное видео.

Сам видео я отредактировал Около реализации ядра Jupyter и включает в себя демонстрацию (не питона) ноутбука, и я рекомендую вам проверить, если это вас интересует.

Autoawait в Jupyter.

С 7.0 выпуска iPython Jupyter поддерживал очень крутую функцию под названием «Autoaavait».

Пост Матиаса входит в подробности о том, что это такое и как его использовать, но вот TL; DR: Если у вас есть ячейка в ноутбуке Jupyter, в которой в этом ключевое слово «a ждет», он попытается запустить его в соответствующем контуре события. Например, вот фрагмент из ноутбука, поднявшись с Asyncio Coroutines:

В этом примере я что-то распечатаю, сна на секунду, а затем снова распечатайте что-то. Поскольку AUTOWAIT настроен на работу с Asyncio, Jupyter прозрачно выясняет, как запустить код в ячейке, как будто это COROUTINE. Вне коробки это работает с Asyncio и может быть настроен на работу с Curio и Трио Используя так называемый Магическая команда называется % Autoaavait – Например, % Awawait Trio использовать его с трио.

Как это работает?

Во-первых, когда вы запускаете ячейку, iPython обнаруживает использование ключевого слова await в вашем коде. Далее он смотрит на функцию, называемую «LOOP_RUNNER», который зарегистрирован внутри iPython для каждой из поддерживаемых бэкэндов. Затем он переписывает ячейку в COROUTINE и передает его в Loop_Runner, что делает эквивалент Asyncio’s run_until_complete – Немного нравится task.reach Отказ Наконец, выход этого COROUTINE возвращается пользователю.

Исходный код наиболее актуален для этого находится в IPython.core.async_helpers а также IPython.core.interactiveshell модули, и это искренне прочитано – Я призываю вас взглянуть.

К сожалению, этот подход – во время которого требуется минимальный рефакторинг кодовой базы iPython – имеет несколько ограничений.

Прежде всего, запуск самих петлей – то есть звонок task.reach (или Reactor.run ) – блокируется. Это означает, что ничто не может произойти внутри Jupyter, пока запущена Autoawied Cell. Это ставит настоящий демпфер на наше веселье – в конце концов, точка Async IO заключается в том, что мы можем сделать более одной вещи за раз!

Во-вторых, этот подход запускает петлю события, управляет COROUTINE, а затем останавливает контур события каждый раз, когда автоматически автоматическая ячейка оценивается. Это не только делает его еще более сложнее делать async io на заднем плане – это также означает, что наивная реализация Autoawait в скрученном виде будет иметь возможность запускать ячейку один раз! Это потому, что реактор с витой предназначен только для начала и остановки ровно – оно будет кричать на тебя очень громко Если вы попытаетесь запустить его во второй раз.

В отличие от Curio и Trio Loop_Runners, используя Asyncio в jupyter не имеет этих ограничений. Это потому, что iPykernel уже имеет бегущую петлю Asyncio и Специальные случаи Autoaavait Чтобы запустить асинсио CONOTINES как обычно, а не использовать предоставленный LOOP_RUNNER. В будущем возможно, что кто-то восправит iPython и iPykernel, чтобы позволить произвольному контуру событий к этому механизму подключаться к этому механизму, но на сегодня это не вообще доступное API.

Как мы можем использовать скрученные с Jupyter?

Таким образом, iPython не поддерживает скрученные в его реализации AUTOAVAIT. Что мы собираемся делать?

Один подход – не использовать Autoawait вообще. Скрученный может необязательно Используйте петлю события Asyncio Это уже работает в Jupyter вместо того, чтобы использовать собственную реализацию контура событий. Это будет работать просто хорошо – никакие магии не нужны! – И наши услуги будут счастливы на заднем плане. На самом деле, глиф и Moshe использовали этот подход в хотя бы один семинар Отказ

Тем не менее, мы пропустим этот сахар – в любое время мы хотим использовать Async/ждут, нам нужно определить COROUTINE с async def. а затем вручную запланировать его, используя Ensedefred , как в нашем примерном примере. Мы также теряем удивительную особенностью AUAVAIT: выход Async Actions, оставаясь с их соответствующими ячейками.

Когда Python Prints выводится на экран, Jupyter не имеет хорошего способа узнать, с какой ячейки возникли, что выводится, кроме знания того, что ячейка работает в первую очередь. Это означает, что если вы создаете объект, который печатает асинхронно на заднем плане:

class NoiseMaker():
    tick = 1

    def _loop(self):
        if self._running:
            print('LOUD NOISES!')
            reactor.callLater(self.tick, self._loop)
    def start(self):
        self._running = True
        self._loop()
    def stop(self):
        self._running = False

noisemaker = NoiseMaker()

noisemaker.start()

Это напечатает материал к выходу этой ячейки до Вы запускаете содержимое другой клетки:

Это не вредный как таковой. Также нет очевидного способа исправить его, не делая Jupyter осознавать данный корабль через механизм AUTOAVAIT. Если вы пишете вещи, которые бегают на заднем плане, что Не Печать на экране, тогда у вас не будет этой проблемы, и если вы пишете вещи, которые завершают печать в разумное количество времени, то ваш вывод будет наверное Перейдите в правую клетку для интерактивного использования. Это работает.

Этот скриншот исходит из ноутбука, который я писал, что демонстрирует это более полно, что вы можете найти В моем Github Repo Отказ

Скажем, мы недовольны статус-кво, и мы мертв, установленные на реализацию Autoaavait для скрученного. Как мы можем сделать это?

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

Для этого мы можем использовать библиотеку под названием Крючком , что делает это для нас и обнажает два декоратора, чтобы помочь нам, называется @ждать и @run_in_reacor. . @ждать Запускает обернутую отложенную функцию, возвращающуюся в витой резьбе и блокирует основную нить, пока она не сделана. Требуется параметр Timeout, который сделает его таким образом, чтобы функция бросала исключение, если похоже, что скрученно принимает слишком долго. @Run_in_reacor похоже, но не имеет блокирующего поведения @ждать и полезно для по-настоящему запущенного витой код на заднем плане . Это вместо этого возвращает Eventualresult Объект, который затем может быть использован для ожидания вывода в то время, когда это удобно сделать.

Учитывая, что мы знаем о вязании крючком, наш подход становится ясным. Мы пишем Расширение iPython , который можно загрузить с % load_ext магия. Это расширение затем устанавливает вязание крючком и запускает витой реактор на заднем плане и регистрирует соответствующий Loop_Runner, который использует @ждать пройти наши Coroutines, чтобы скрутить. Наконец, мы регистрируем еще несколько магических веществ, которые позволяют настроить вязание крючком и сделать вызовы Async Background Calls to @Run_in_reacor Отказ

Использование моей реализации этой идеи выглядит так:

В этом примере я загружаю расширение и настроить AUTOAVAIT:

%load_ext twisted_ipython
%autoawait twisted

Тогда я могу случайно использовать ждут с витой:

print('Going to sleep...')

await sleep(1)

Shout('I HAVE AWAKENED!')

Я использовал это для моих искривленных проектов за последний год или около того, и для меня он работает довольно хорошо. Это дает мне полнофункциональный скрученный вид % Autoaavait И это позволяет мне запустить витой код без блокировки iPython – все очень круто!

Однако есть несколько нисходящих.

Одним из состоит в том, что это требует как вязание крючком, так и скрученным в виде необязательных зависимостей. IPython способна импортировать Curio и Trio Lazy в своих реализациях, и поэтому не приходится на самом деле их отправить – это делает добавление запеченной – в поддержку гораздо более наполнено, чем реализация, которая потребует доставки целую структуру, кто-то может даже не использовать.

Другой, который, как я думаю, является более неудачным, это то, что для кода мы хотим запустить на заднем плане, которые используют скрученные, нам теперь нужно использовать крючком @Run_in_reacor Декоратор – или точнее, мой расширение % run_in_reacor магия. Для меня это достойная компромисс, поскольку мои более типичные случаи использования вокруг интерактивных запросов хорошо подходят для Autoawait, чем мой менее распространенный случай использования нерестовых серверов на заднем плане.

Twisted_ipython Живет на Github и может быть установлен из Пейпина . Я надеюсь, что этот фон интересна и будет так, если бы люди оказались, используя мою библиотеку!

Оригинал: “https://dev.to/jfhbrook/twistedipython-autoawait-in-jupyter-notebooks-with-twisted-lee”