четверг, 15 марта 2012 г.

Декораторы. Поддержка множества экземпляров

Есть в Python такая штука, как декораторы. По сути, это обёртка для класса или функции, которая позволяет обслуживать создание экземпляров классов или вызов функций и оборачивать их дополнительной логикой. Есть в них несколько тонкостей, но одна из них у меня вызвала затруднение, разрешением которого я и хочу поделиться. Декоратор может быть определён как класс или как функция. И если определить декоратор класса как класс, то возникает одна проблема: создание каждого нового экземпляра затирает предыдущий. Для того, чтобы выяснить причину этого, я немного адаптировал пример из книжки Лутца "Изучаем Python". Вот что происходит, если декоратор определить как функцию:

PEP 3155: квалифицированные имена для классов и функций

Причина
Инструменты интроспекции Python долгое время имели плохую поддержку для вложенных классов. Имея объект класса невозможно узнать, где он был определён: внутри другого класса или на верхнем уровне модуля, и в первом случае так же не возможно узнать, в каком классе находится его определение. Так как использование вложенных классов часто признаётся дурным стилем программирования, то то, что единственная причина определения второго класса для того, чтобы получить возможность интроспекции другого вложенного класса, оказывается дурным каламбуром.

среда, 14 марта 2012 г.

PEP 409: подавление контекста исключений

Суть
Одна из открытых проблем со времён PEP 3134 — подавление контекста, так как на тот момент решения не было найдено. Данный PEP исправляет это упущение.
Причина
Есть два способа создать исключение:

  1. Python сам делает это (ошибки кода, отсутствующие ресурсы, завершение циклов и т.д.)
  2. ручками (с помощью выражения raise)
В процессе написания библиотек или пользовательских классов часто необходимо возбуждать исключения; более того, часто полезно, или даже нужно, заменить одно исключение на другое. Вот пример из модуля dbf автора:
try:
    value = int(value)
except Exception:
    raise DbfError(...)
По любому начальное исключение (ValueError, TypeError или что-то в этом роде) уже не релевантно. С этого момента нам нужно только DbfError, однако, если это исключение будет выведено, то мы увидим информацию об обоих исключениях.
Альтернативы
Были предложены следующие возможности решения:

  • raise as NewException()
    Используется ключевое слово as. Может ввести в заблуждение, так как на самом деле мы не перевозбуждаем начальное исключение
  • raise NewException() from None
    В русле текущего синтаксиса объявления предыдущего исключения
  • exc=NewException(); exc.__context__=None; raise exc
    Более многословный способ предыдущего варианта
  • raise NewException.no_context(...)
    Создаем метод класса для подавления контекста
Предложение
Я предлагаю использовать второй вариант:
raise NewException from None
В данном случае мы имеем преимущество в том, что используем существующий шаблон для установки причины возбуждения исключения:
raise KeyError() from NameError()
но поскольку причиной будет None, предыдущий контекст не будет отображаться стандартной процедурой отображения исключения.
Обсуждение реализации
На данный момент None является  значением по умолчанию для __context__ и __cause__. Для обеспечения поддержки raise ... from None (что должно установить значение __cause__ в None) нам нужно другое значение по умолчанию для __cause__. Было предложено несколько идей, как это реализовать на уроне языка:

  • Переписать информацию в имеющемся исключении (пошагово и оставить значение None для  __cause__)
    Отклонено, так как это может серьезно помешать отладке из-за плохого сообщения об ошибке
  • Использовать одно из булевых значений в  __cause__: False может быть значением по умолчанию и при использовании from ... будет заменено либо на соответствующее исключение, либо на None
    Отклонено, так как поощряет использование двух разных типов объектов для  __cause__, причем для одного из этих типов (булева) не возможно использовать полный спектр значений (True не будет использовано)
  • Создать специальный класс исключений __NoException__
    Отклонено, так как возможно будет вводить в заблуждение или будет по ошибке возбуждено пользователями и не является уникальным значением, как None, False и True.
  • Использовать Ellipsis (многоточие) для значения по умолчанию
    Принято.
Многоточие в английском используются там, где опущены какие-либо слова. Так что в нашем случае это будет означать: "__cause__ опущено, так что смотрите в __context__ для получения более подробной информации".
Ellipsis  так же не является исключением, так что не может быть возбуждено.
Есть только Ellipsis, так что не будет неиспользованных значений
Информация об ошибке не выбрасывается, то что пользовательский код может провести трассировку цепи исключений при том, что по умолчанию это не происходит.
Детали языка
Для поддержки raise Exception from None, __context__ останется каким и был, а  __cause__  получит значение по умолчанию Ellipsis и будет заменено на None при использовании выражения  Exception from None.
форма __context__ __cause__
raise None Ellipsis
повторное raise предыдущее исключение Ellipsis
повторное raise from None | ChainedException предыдущее исключение None | цепь исключений
Процедура вывода исключения по умолчанию будет работать так:

  • если  __cause__ равно Ellipsis, __context__ (если есть) будет выведен
  • если  __cause__  равно None, __context__ не будет выведен
  • если  __cause__  содержит любое другое значение, будет отображено  __cause__  
В двух последних случаях следование по цепи исключений будет прекращено.
Поскольку значение по умолчанию для __cause__ теперь Ellipsis, a raise Exception form Cause лишь более короткая форма для:
_exc = NewException()
_exc.__cause__ = Cause()
raise _exc
Ellipsis, как и None, теперь может быть "причиной":
raise Exception from Ellipsis

Выражение raise


raise_stmt ::= "raise" [expression ["from" expression]]
В случае отсутствия expression, повторно возбуждается последнее исключение, которое было активно в данной области. Если такого исключения нет, то возбуждается исключение RuntimeError, чтобы сообщить о данной ошибке.
В противном случае raise выполняет первый expression и получает объект исключения. Он должен являться либо подклассом либо экземпляром BaseException. Если первый expression является именем класса, то создается объект путём вызова класса без передачи аргументов.
Типом данного исключения является класс экземпляра исключения, а значением — сам экземпляр.
Объект трассировки обычно создается автоматически при возбуждении исключения и присоединяется к атрибуту исключения __traceback__, доступному для записи. Вы можете создать исключение и сами установить свою собственную трассировку используя  метод исключения with_traceback() (который возвращает тот же самый экземпляр исключения с установленным аргументом трассировки), как например:
raise Exception("foo occurred").with_traceback(tracebackobj)
Оператор from используется для создания цепочки исключений: если он присутствует, то второй expression должен быть другим классом или экземпляром исключения, которое будет присоединено к возбуждаемому исключению в качестве атрибута  __cause__ (доступного для записи). Если возбуждаемое исключение не перехватывается, то оба исключения будут напечатаны:
>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc...

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:
/*Исключение выше было прямой причиной следующего исключения*/
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
Похожий механизм работает если исключение возникло в процессе перехвата исключения: первое исключение будет присоединено к атрибуту __context__ нового исключения:
>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:
/*В процессе перехвата вышеуказанного исключения возникло новое исключение:*/

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
Дополнительную информацию про исключения можно найти в разделе Исключения, а про перехват исключений в разделе выражение try.

вторник, 13 марта 2012 г.

PEP 3151: Реструктуризация иерархии исключений OS и IO


Вступление:
Стандартная иерархия исключений — важная часть языка Python. При этом она имеет два определяющих свойства: она с одной стороны общая, а с другой - селективная. Общая, в данном случае, значит, что один и то же тип исключения может быть возбуждён и перехвачен вне зависимости от контекста (например, когда вы пытаетесь прибавить что-то к числу, вызвать строковый метод или записать объект в сокет, при неверных аргументах будет вызвано исключение TypeError). Селективное означает, что мы можем какие-то исключения перехватывать, обрабатывать, сохранять, инкапсулировать, а какие-то выпускать на более высокий уровень перехвата. Например, вы можете перехватывать ZeroDivisionErrors и пропускать другие типы ArthmeticErrors (например, OverflowError).
Данный PEP предлагает внести изменения в часть этой иерархии для лучшего воплощения данных принципов; он касается ошибок, связанных с системными вызовами (OSError, IOError, mmap.error, select.error и все их подклассы).

четверг, 8 марта 2012 г.

object.__del__(self) 2.7

Вызывается когда экземпляр должен быть уничтожен (другими словами — это деструктор). Если родительский класс тоже имеет метод __del__(), то производный класс в своём методе __del__(), если он определён, должен явно вызывать метод родительского класса, чтобы гарантированно уничтожить методы родительского класса. Стоит отметить, что возможно (хотя и не рекомендуется) сделать так, чтобы в методе __del__() было отложено уничтожение самого объекта. Это достигается созданием на него другой ссылки перед удалением текущей, и уже при уничтожении последней ссылки надо будет уничтожить сам объект. Гарантии того, что метод __del__() будет вызван для существующих объектов при завершении работы интерпретатора нет.

del x и x.__del__()

Залез я сегодня на сайт Яндекса и посмотрел, какие задачи даются для проверки знаний на Python. И уже первая вызвала у меня затруднения)) Вот она:
Что выведет следующий код:

вторник, 6 марта 2012 г.

PEP 380: Синтаксис делегации субгенератору

Аннотация:
Данный синтаксис предназначен для того, чтобы генератор мог передать часть своих функций другому генератору. Другими словами, часть кода, содержащую yield можно вынести и поместить в отдельную функцию. Кроме того, субгенератор может возвращать значение с помощью return, и это значение будет доступно делегирующему генератору. Благодаря этому синтаксису можно добиться оптимизации в случае, если один генератор должен выдавать значения другого генератора.