Sergey (Nolar) Vasilyev ([info]nolar) wrote,

Python Fun #2: generators and exceptions

Возмём простую задачу, но заюзаем прикольную фичу Python'а -- генераторы, которые, согласно документации, позволяют "замораживать выполнение функции до обращения за следующим результатом" (примерно так). Не, это не многопоточное выполнение; это лишь синтаксическая условность, обозначающая что на операторе yield мы как будто выходим из функции с результатом, делаем что-то снаружи, и потом опять возвращаемся в функцию для продолжения.

Напишем функцию, которая возвращает последовательность трёх элементов: чисел 1,2,3. Простейшее решение -- return [1,2,3]. Но мы напишем функцию, которая возвращает результат с помощью генераторов, а также имеет инициализатор в начале (строка 2). И пройдёмся по её результату для вывода на stdout (строки 19-22).

Забавы ради, иницилизатор сделаем "глючным", и он нам будет выкидывать исключение (строка 2). Но мы умные, мы исключения от этой функции ожидаем, и будем их ловить (строка 14), и в таком случае результат подразумевать пустым (строка 16).

Просто, да? Вот код:

  1. def generator():
  2.     raise Exception("dieplz")
  3.     yield 1
  4.     yield 2
  5.     yield 3
  6.  
  7.  
  8. try:
  9.     print('==============================')
  10.     try:
  11.         print("Calling function")
  12.         items = generator()
  13.         print("Call succeded")
  14.     except Exception, e:
  15.         print("Call failed with exception: %s" % e)
  16.         items = []
  17.     print('==============================')
  18.    
  19.     print("Iterating over items")
  20.     for item in items:
  21.         print("Item: %s" % item)
  22.     print("Iteration finished")
  23. except Exception, e:
  24.         print("Failed with unhandled exception: %s" % e)
  25. finally:
  26.         print("Done")
  27. print('==============================')
  28.  



Но не тут-то было! Эта функция НЕ выполняется в момент вызова функции (на строке 12). Вместо этого на невидимой прослойке интерпретатора возвращается некий объект-генератор, который попадает в переменную items. И только попытка итерации по нему (строка 20) уже реально вызывает функцию.

И исключение, которое в функции должно происходить даже до первой "заморозки выполнения", случается не в том месте, где его ждут (на строке 20 вместо ожидаемой 12). И мы ловим unhandled exception вместо итерации по пустому анти-ошибочному списку.

>python generators.py
==============================
Calling function
Call succeded
==============================
Iterating over items
Failed with unhandled exception: dieplz
Done
==============================


Решение проблемы простое - всегда пригонять результаты генератором в известный тип: items = list(generator()). Но это решение неудобное. А если эта функция - callback, который задаётся кем-то снаружи? А если им захочется передать не колбек, а просто экземпляр итерабельного объекта? А если этот объект ещё изменит своё состояние до цикла for, и итерация действительно должна быть только в том месте?

Не без изъянов, в общем, язычок-то.
Tags: python

  • Post a new comment

    Error

    Your reply will be screened

    Your IP address will be recorded 

  • 12 comments

[info]jahson

December 17 2009, 03:07:39 UTC 2 years ago

Нет, ну чего ты ждал? Там ясно, человечьим языком, написано, что при вызове функции, определённой с yield вернётся generator iterator, генерирующий итератор, его за ногу. Тело генератора исполняется при вызове .next() - собственно тогда и вернётся твой эксепшн, а вовсе не во время вызова generator().

Так что твоя проблема - в недопонимании того, что вызывая generator() ты вызваешь не тело функции, а всего лишь создаёшь генератор, т.к. функция, определённая с yield превращается в функцию-генератор )


Using a yield statement in a function definition is sufficient to cause that definition to create a generator function instead of a normal function. When a generator function is called, it returns an iterator known as a generator iterator, or more commonly, a generator.


Вот там курили:
http://www.python.org/dev/peps/pep-0255/
http://www.python.org/dev/peps/pep-0342

[info]nolar

December 17 2009, 04:53:10 UTC 2 years ago

Я мануал читал, знаю это. Проблема в неочевидности синтаксиса. Если бы функция отрабатывала хотя бы до первого yield - было бы уже куда очевиднее.

[info]pavel_kudinov

January 4 2010, 18:05:22 UTC 2 years ago

хм, а какая тогда разница где именно написать yield? а если разницы нет, почему это не опция конструктора?

p.s. а вообще, сам на Python давно поглядываю и всем его рекомендую на вопрос "какой язык учить", как универсальный язык, но сам сижу на perl по историческим причинам.

[info]nolar

January 4 2010, 20:05:42 UTC 2 years ago

Я перл уже два раза забыть успел, причём буквально :-)

Один раз учил и практиковал когда админил/со-админил у провайдера и для себя. Всякие скрипты, парсеры, и пр.

Второй раз учил когда думал туда переползти с пхп; но, увидев тамошний "ООП", испугался и вытер из памяти чтоб не повредить своё мировоззрение %-)

[info]pavel_kudinov

January 4 2010, 22:13:04 UTC 2 years ago

>чтоб не повредить своё мировоззрение %-)

в чём-то ты прав, счастье в неведении: копнув глубже, можно усомниться: ООП вообще хреновая парадигма, странно что до сих пор не сдохла, её век должен был закончиться на С++.

Инкапсулировать данные в наборы методов воздействия на них можно множеством других способов, например замыканиями (которые есть и в Perl (потому я ещё на нём), и в JS (потому я его оценил), и в Python (потому я его рекомендую)).

Вообще, предел интегральной сложности для современной вершины парадигм разработки (ООП) настанет довольно скоро, но прыгнем мы скорее всего сразу в декларативное программирование (если осилят разработку приличных оптимизаторов, осуществляющих алгоритмическую декомпозицию лучше человека), минуя эпоху lazy* и closures.

[info]nolar

January 4 2010, 22:25:20 UTC 2 years ago

Это да, может быть и плохая. Недаром от неё норовят отпочковаться всякие аспектные и прочие парадигмы. Но она такая живучая, потому что лучших решений пока нет. Функциональное программирование - удел касты математиков с вывихнутыми на рекурсии мозгами. Массовому программисту "только что из школы" оно не по силам. Вот и живёт ООП монопольно.

Замыкания - ок, есть в питоне красивые, есть в JS. Если JS не пошёл в массы поскольку сфера применения ограничена по факту (хотя что мешает расширить), то почему питон не идёт - непонятно. Видимо, замыкания - удел гуи и прочего интерактивного, как альтернативное решение для передачи состояния. Не вебовское точно, не скриптовое.

Декларативное ждём-с. Пока что это вещь в себе, судить не о чем. Хотя, кстати... А чё ждать? У меня с 2002 года в голове концепты и наброски как программировать на естественном языке и как это парсить и интерпретировать. Надо брать и разрабатывать. Ай, лентяй :-)

[info]pavel_kudinov

January 4 2010, 22:43:36 UTC 2 years ago

>Но она такая живучая, потому что лучших решений пока нет
>Массовому программисту "только что из школы" оно не по силам

нет, это потому, что сейчас период массового программирования, заполняются ниши. после заполнения ниш начнётся конкуренция среди разработчиков, школота сгинет и вместе с разработчиками начнут конкурировать и средства разработки

> Если JS не пошёл в массы

пошёл: ещё немного и почти вся прикладнуха будет в браузере на стороне клиента

> почему питон не идёт - непонятно

в целом идёт, смотри хотя бы в сторону империи зла (Google), .ру просто как всегда самобытно развивается

> Видимо, замыкания - удел гуи и прочего интерактивного, как альтернативное решение для передачи состояния. Не вебовское точно, не скриптовое.

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

> Пока что это вещь в себе, судить не о чем

SQL давно и прочно тут, XSLT тоже, Haskel не щупал не знаю в чём там проблема, но да - это всё слишком криво, хотя главное - оптимизаторы. Декларативное должно формировать лучший (в силу машинной алгоритмической оптимизации, автоматической распределяемости на треды и асинхронные сетевые запросы), а скоро и просто единственно возможный код (в силу того, что распределяемость, автооптимизация и асинхронность станут жизненно-важной необходимостью в силу сложности решаемых задач, что вручную делать нереально).

[info]nolar

January 4 2010, 23:13:28 UTC 2 years ago

Дай бог, но это всё на годы и годы вперёд. Не здесь, не сейчас. А пока что ООП рулит, потому что ООП рулит :-)

[info]pavel_kudinov

January 4 2010, 23:37:15 UTC 2 years ago

без ООП можно жить, и очень неплохо, даже в сегодняшнем суровом коммерческом программировании.

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

как всегда, контекст превыше всего

[info]surger

December 18 2009, 17:59:25 UTC 2 years ago

Пообщался с другом

xxx: можешь сказать в чем главная ошибка
yyy: он изначально рассматривает генератор как функцию и ожидает что она сразу начнет выполняться
xxx: хм, а как надо?
yyy: вызов генератора лучше рассматривать как создание объекта
xxx: понятно, спасиб

[info]nolar

December 18 2009, 18:42:15 UTC 2 years ago

Ну это понятно. Если знать заранее. Но синтаксис fn() не намекает на создание объекта. Пример:

def doit(callback):
  try: items = callback()
  except: items = []
  for item in items:
    print(item)

def fn1(): return [1,2,3]
def fn2(): yield 10

doit(fn1)
doit(fn2)
 


Простой, казалось бы, синтаксис у doit. Вменяемый. А вот юз-кейс подкачал. Потому что "фокус".

[info]nolar

December 18 2009, 18:46:41 UTC 2 years ago

PS: То есть проблема в том, что fn2 объявлена как "def" (фукнция, а не что-то там типа "class" или т.п.). И внутренности у неё не являются фабрикой, кроме как за счёт yield'а, который по сути более похож на "return value", чем на "return lazy_iterator([value])".
Create an Account
Forgot your login or password?
Facebook Twitter More login options
English • Español • Deutsch • Русский…