Я начал использовать Twisted в проекте, который требует асинхронного программирования, и документы довольно хороши.
Итак, мой вопрос в том, является ли отсрочка в Twisted тем же самым, что и обещание в Javascript? Если нет, каковы различия?
Я начал использовать Twisted в проекте, который требует асинхронного программирования, и документы довольно хороши.
Итак, мой вопрос в том, является ли отсрочка в Twisted тем же самым, что и обещание в Javascript? Если нет, каковы различия?
Ответ на ваш вопрос: Да и Нет, в зависимости от того, почему вы спрашиваете.
Оба Twisted Deferred
и Javascript Promise
реализуют механизм для очереди на выполнение синхронных блоков кода, которые должны выполняться в заданном порядке, будучи отделенными от других синхронных блоков кода.
Таким образом, Javascript Promise
на самом деле больше похож на Python Future
, и простой способ объяснить это состоит в том, чтобы говорить о том, что Promise
и Resolver
объединяются, чтобы сделать Deferred
, и заявить, что это влияет на то, что вы можете делать с обратными вызовами.
Это очень хорошо и хорошо, что он точен, однако на самом деле он не делает ничего более ясного и не набирает тысячи слов, где я почти гарантированно допущу ошибку, я, вероятно, лучше цитирую кого-то который немного знает о Python.
Здесь моя попытка объяснить Отложенные большие идеи (и их много из них) для продвинутых пользователей Python без предыдущего опыта Twisted. Я также предполагаю, что вы раньше думали об асинхронных вызовах. Просто чтобы раздражать Символ, я использую 5-звездную систему, чтобы указать важность идей, где 1 звезда - "хорошая идея, но довольно очевидная" и 5 звезд "блестящий".
Я показываю много фрагментов кода, потому что некоторые идеи просто лучшие так выразился, но я намеренно оставляю много деталей, и иногда я показываю код с ошибками, если их исправление уменьшит понимая идею кода. (Я укажу на такие ошибки.) Я использую Python 3.
Заметки для Glyph: (a) Рассмотрите этот проект для блога после. Я был бы более чем счастлив внести исправления и предложения для улучшения. (b) Это не значит, что я собираюсь изменить Тюльпа на более отложенная модель; но для другого потока.
Идея 1: Возвращает специальный объект вместо принятия аргумента обратного вызова
При разработке API, которые генерируют результаты асинхронно, вы обнаруживаете, что вам нужна система для обратных вызовов. Обычно первый дизайн, который приходит ум должен передать функцию обратного вызова, которая будет вызываться, когда асинхронная операция завершена. Я даже видел проекты, где, если вы не передать в обратном вызове операцию синхронно - это достаточно плохо Я бы дал ему нулевые звезды. Но даже однозвездная версия загрязняет все API-интерфейсы с дополнительными аргументами, которые нужно проделать утомительно. Сначала витая первая большая идея заключается в том, что лучше вернуть специальный объект, к которому вызывающий может добавить обратный вызов после его получения. я дайте эти три звезды, потому что от этого прорастают так много других хороших идеи. Это, конечно, похоже на идею, лежащую в основе фьючерсов и Promises, найденный на многих языках и в библиотеках, например. Питона concurrent.futures(PEP 3148, тесно связанный с Java Futures, оба которые предназначены для резьбового мира), а теперь Tulip (PEP 3156, используя аналогичная конструкция, адаптированная для асинхронной работы без резьбы).
Идея 2: передать результаты обратного вызова для обратного вызова
Я думаю, что лучше всего сначала показать код:
класс Отложенный: def init (self): self.callbacks = [] def addCallback (self, callback): self.callbacks.append(callback) # Ошибка здесь def callback (self, result): для cb в self.callbacks: result = cb (результат)
Наиболее интересными битами являются последние две строки: результат каждого обратный вызов передается следующему. Это отличается от того, как все работает в concurrent.futures и Tulip, где результат (после набора) фиксирован как атрибут Будущего. Здесь результат может быть изменен каждым Обратный вызов.
Это позволяет создать новый шаблон, когда одна функция возвращает Отложенное вызывает другой и преобразует его результат, и это то, что зарабатывает эта идея три звезды. Например, предположим, что у нас есть асинхронная функция который читает набор закладок, и мы хотим написать асинхронную функцию который вызывает это, а затем сортирует закладки. Вместо того, чтобы изобретать механизм, при котором одна асинхронная функция может ждать другого (что мы будет делать это в любом случае:-), вторая асинхронная функция может просто добавить новый обратный вызов для Отложенного, возвращенный первым:
def read_bookmarks_sorted(): d = read_bookmarks() d.addCallback(отсортированный) return d
Отложенная функция, возвращаемая этой функцией, представляет собой отсортированный список закладки. Если вызывающий абонент хочет распечатать эти закладки, он должен добавить другой обратный вызов:
d = read_bookmarks_sorted() d.addCallback(печать)
В мире, где результаты асинхронизации представлены Futures, этот же Например, потребуется два отдельных фьючерса: один возвращается read_bookmarks(), представляющий несортированный список, и отдельное будущее возвращенный read_bookmarks_sorted(), представляющий отсортированный список.
В этой версии класса есть одна неочевидная ошибка: if addCallback() вызывается после того, как Отсрочка уже запущена (т.е. ее callback() был вызван), то обратный вызов, добавленный addCallback() никогда не будет называться. Это достаточно легко исправить, но утомительно, и вы можете посмотреть его в исходном коде Twisted. Я буду носить эту ошибку через последовательные примеры - просто притворись, что ты живешь в мире где результат не будет готов слишком скоро. Есть и другие проблемы с этим дизайном тоже, но я бы скорее назвал улучшения решений чем исправления.
Кроме того: скрученные плохие варианты терминологии
Я не знаю, почему, но, начиная с собственного имени проекта, Twisted часто потирает меня неправильным путем с выбором имен для вещей. Для Например, мне очень нравится, что имена классов должны быть существительными. Но "Отсроченный" - это прилагательное, а не просто какое-либо прилагательное, это глагол прошлого причастия (и слишком длинный в этом:-). И почему это в модуле с именем twisted.internet?
Затем появляется "обратный вызов", который используется для двух связанных, но разных цели: это предпочтительный термин, используемый для функции, которая будет когда результат готов, но это также имя метода вы вызываете "огонь" отложенного, т.е. устанавливаете (исходный) результат.
Не заставляйте меня начинать с неологизма /portmanteau, который является "ошибкой", что приводит нас к...
Идея 3: Интегрированная обработка ошибок
Эта идея получает только две звезды (что, я уверен, разочарует многих Искривленные поклонники), потому что это меня смутило. Я также отметил, что У витых документов есть некоторые проблемы, объясняющие, как это работает. В этом случае в частности, я обнаружил, что чтение кода было более полезным, чем документы.
Основная идея достаточно проста: что, если обещание уволить Отсрочка с результатом не может быть выполнена? Когда мы пишем
d = pod_bay_doors.open() d.addCallback(lambda _: pod.launch())
как HAL 9000 должен сказать: "Извините, Дэйв. Боюсь, я не могу сделайте это??
И даже если мы не будем отвечать за этот ответ, что нам делать, если один из вызывает обратные вызовы?
Скрученное решение состоит в том, чтобы раздвоить каждый обратный вызов на обратный вызов и" ошибка ". Но это не все - для устранения исключений вызванный обратными вызовами, он также вводит новый класс" Отказ ". Я бы на самом деле хотелось бы ввести последнее сначала, не вводя errbacks:
класс Неудача: def init (self): self.exception = sys.exc_info() 1
(Кстати, отличное имя класса. И я имею в виду это, я не являюсь саркастичный.)
Теперь мы можем переписать метод обратного вызова() следующим образом:
def callback(self, result): for cb in self.callbacks: try: result = cb(result) except: result = Failure()
Это само по себе я бы дал две звезды; обратный вызов может использовать isinstance (result, Failure), чтобы рассказать о регулярных результатах, кроме неудачи.
Кстати, в Python 3 можно было бы покончить с отдельные классы исключения, содержащие исключения для инкапсуляции, и просто используйте встроенный класс BaseException. Из чтения комментариев в коде, Класс Twisted Failure в основном существует, так что он может содержать все информация, возвращаемая sys.exc_info(), то есть класс/тип исключения, экземпляр исключения и трассировка, но в Python 3 объекты исключений уже содержат ссылку на трассировку. Есть некоторые отладочные материалы что класс Twisted Failure делает стандартные исключения, но Тем не менее, я думаю, что большинство причин для введения отдельного класса были адрес.
Но не забывайте об ошибках. Мы меняем список обратные вызовы к списку пар функций обратного вызова, и мы переписываем callback() снова, следующим образом:
def callback(self, result): for (cb, eb) in self.callbacks: if isinstance(result, Failure): cb = eb # Use errback try: result = cb(result) except: result = Failure()
Для удобства мы также добавляем метод errback():
def errback(self, fail=None): if fail is None: fail = Failure() self.callback(fail)
(Функция real errback() имеет еще несколько особых случаев, это может быть вызываемый либо с исключением, либо с ошибкой как аргумент, и Класс Failure принимает необязательный аргумент исключения, чтобы предотвратить его используя sys.exc_info(). Но ничто из этого не является существенным, и это делает более сложные фрагменты кода.)
Чтобы убедиться, что self.callbacks - это список пар, мы должны также обновить addCallback() (он по-прежнему не работает при вызове после Отсрочка уволилась):
def addCallback(self, callback, errback=None): if errback is None: errback = lambda r: r self.callbacks.append((callback, errback))
Если это вызвано с помощью только функции обратного вызова, это будет фиктивный элемент, который передает результат (т.е. экземпляр Failure) через без изменений. Это сохраняет условие ошибки для последующей ошибки обработчик. Чтобы упростить добавление обработчика ошибок без обработки регулярный resullt, добавим addErrback() следующим образом:
def addErrback(self, errback): self.addCallback(lambda r: r, errback)
Здесь половина обратного вызова пары будет передавать результат (не сбой) без изменений до следующего обратного вызова.
Если вам нужна полная мотивация, прочитайте Twisted Introduction to Deferreds; Я просто закончу, заметив, что ошибка и замените регулярный результат для отказа, просто вернув значение, не относящееся к ошибке (включая None).
Прежде чем перейти к следующей идее, позвольте мне отметить, что есть больше тонкости в реальном классе отсрочки. Например, вы можете указать дополнительные аргументы, которые необходимо передать обратному и ошибочному. Но в вы можете сделать это с помощью лямбда, поэтому я оставляю это, потому что дополнительный код для администрирования не объясняет основные идеи.
Идея 4: Отсрочка цепочек
Это пятизвездочная идея! Иногда это действительно необходимо для обратный вызов для ожидания дополнительного асинхронного события, прежде чем он сможет произвести желаемый результат. Например, предположим, что у нас есть две основные асинхронные операций, read_bookmarks() и sync_bookmarks(), и мы хотим, чтобы комбинированная работа. Если это был синхронный код, мы могли бы написать:
def sync_and_read_bookmarks(): sync_bookmarks() return read_bookmarks()
Но как мы пишем это, если все операции возвращают Отсрочки? С идея цепочки, мы можем сделать это следующим образом:
def sync_and_read_bookmarks(): d = sync_bookmarks() d.addCallback(lambda unused_result: read_bookmarks()) return d
Лямбда нужна, потому что все обратные вызовы вызываются с результатом значение, но read_bookmarks() не принимает аргументов.