понедельник, 24 декабря 2012 г.

Отчет по спринту

Всё получилось!

Собралось нас семеро: Вова Гоцик, Лена Кривонос, Витя Ершов, Олег Чубин, Антон Касьянов, Паша Коломиец и я.

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

Главное, все приобщились к делу и поняли как идет процесс работы над кодом.

Съели пиццу и разошлись довольные друг другом, оставив меня разгребать результаты.

Пара фоток с мероприятия:



Спасибо всем кто пришёл!

воскресенье, 23 декабря 2012 г.

Ищем докладчиков на Kyiv.py #9

Всем привет.

Как-то за конференциями, спринтами и подготовкой к будущим событиям мы с Вовой Гоциком забыли про

Kyiv.py #9

Который состоится в 11.00 19 января в офисе Циклума, что на Амосова 12.

Есть всё кроме докладчиков. 
Вернее, один доклад уже есть — это Олег Чубин с интересным рассказом об итераторах-генераторах.
Он подавал эту тему как заявку на UA PyCon 2012, но к сожалению к тому моменту у нас закончились свободные слоты в расписании.

Если у вас есть интересная тема — пишите мне на andrew.svetlov@gmail.com
Очень надеюсь что наберем достаточно желающих — иначе мне придется закрывать дыру своей тушкой :)

четверг, 20 декабря 2012 г.

Спринт

Напоминаю: в воскресенье 23 декабря состоится спринт по Питону. Подробности — здесь

понедельник, 17 декабря 2012 г.

Исключения в Питоне

Поговорим об исключениях.

Всё нижеизложенное относится к Python 3.3, хотя отчасти справедливо и для более ранних версий.

Для чего они применяются, наверное, все и так прекрасно знают: для передачи сообщений об ошибках внутри программы.

Рассмотрим простейший пример: открытие файла. Если всё нормально — open(filename, 'r') возвращает объект этого самого файла, с которым можно делать всякие полезные вещи: читать из него данные и т.д.

Если файл не может быть открыт — выбрасывается исключение:

try:
    f = open(filename, 'r')
    try:
        print(f.read())
    finally:
        f.close()
except OSError as ex:
    print("Cannot process file", filename, ": Error is", ex)

Открываем файл и печатаем его содержимое.

Обратите внимание: файл нужно не только открыть но и закрыть после использования. Исключение может выбросить open (например, если файла нет на диске или нет прав на его чтение).

Если файл открыт — читаем его через f.read(). Этот вызов тоже может выбросить исключение, но файл закрывать всё равно нужно. Поэтому необходим блок finally: f.close() должен быть вызван даже если f.read() сломался. В этом месте удобней было бы воспользоваться конструкцией with но мы же сейчас говорим об исключениях а не о контекстных менеджерах, верно?

Исключения из обоих мест попадут в except OSError, где можно будет что-то сделать с ошибкой.

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

Введение закончено. Теперь сконцентрируемся на том что происходит в except.

Типы исключений

Все исключения составляют иерархию с простым наследованием. Вот простой небольшой кусочек от довольно обширного набора исключений ядра Питона:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- AssertionError
      +-- AttributeError
      +-- BufferError

Самый базовый класс — BaseException. Он и его простые потомки (SystemExit, KeyboardInterrupt, GeneratorExit) не предназначены для перехвата обыкновенным программистом — только Питон и редкие библиотеки должны работать с этими типами. Нарушение правила ведет, например, к тому что программу невозможно корректно завершить — что совсем не хорошо.

Также не нужно перехватывать все исключения:

try:
    ...
except:
    ...

работает как

try:
    ...
except BaseException:
    ...

Всё, что может быть нужно программисту — это Exception и унаследованные от него классы.

Вообще-то лучше ловить как можно более конкретные классы исключений. Например, в случае с файлом это OSError или даже может быть FileNotFoundError. Таким образом мы не перехватим AttributeError или ValueError, которые в этом примере означали бы ошибку или опечатку программиста.

Кстати, обратите внимание: StopIteration порожден от Exception а GeneratorExit от BaseException. Подробности, почему сделано именно так, можно найти в PEP 342.

Цепочки исключений

Прочитав предыдущую главку все прониклись необходимостью указывать правильный класс исключений и пообещали никогда не использовать BaseException.

Идем дальше. Следующий пример:

try:
    user = get_user_from_db(login)
except DBError as ex:
    raise UserNotFoundError(login) from ex

Получаем пользователя из базы данных чтобы что-то потом с ним сделать. get_user_from_db может выбросить ошибку базы данных. Для нас это скорее всего означает что такого пользователя нет. Но для логики приложения полезней наш собственный тип UserNotFoundError с указанием логина проблемного пользователя, а не обезличенная ошибка БД — что мы и выбрасываем в обработчике исключения.

Проблема в том, что программисту часто хотелось бы знать, а почему это пользователь не найден. Например, чтобы сохранить в логах для дальнейшего разбирательства.

Для таких целей служит конструкция raise ... from ....

По PEP 3134 у объекта исключения имеется несколько обязательных атрибутов.

В первую очередь это __traceback__, содержащий кусочек стека от места возникновения исключения до места его обработки.

Затем — __context__. Если исключение было создано в ходе обработки другого исключения (выброшено из except блока) — __context__ будет содержать то самое породившее исключение. Которое, в свою очередь тоже может иметь установленный __context__. Этот атрибут равен None если наше исключение — самое первое и не имеет предшественников.

__context__ устанавливается автоматически.

В отличие от контекста __cause__ устанавливается только если исключение было выброшено конструкцией raise ... from ... и равно значению from.

Если исключение выбрасывалось простым raise ... то __cause__ будет равно None в то время как __context__ всегда будет содержать породившее исключение если оно существует.

Для вывода исключения со всей информацией служит набор функций из модуля traceback, например traceback.print_exc().

И тут тоже есть проблема: печатается либо явная цепочка если есть установленный __cause__ или неявная, тогда используется __context__.

Иногда программисту может быть нужно отбросить породившие исключения как не имеющие смысла при выводе traceback. Для этого появилась форма записи

raise exc from None

PEP 409 и PEP 415 рассказывают как это работает:

У исключения всегда есть атрибут __supress_context__. По умолчанию он равен False.

Конструкция raise ... from ... записывает from в __cause__ и устанавливает __supress_context__ в True.

Тогда семейство функций traceback.print_exc() печатают цепочку если явно указан (не равен None) __cause__ или есть __context__ и при этом __supress_context__ равен False.

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

Семейство OSError

Последняя проблема о которой хотелось бы рассказать — это типы исключений порожденные вызовами операционной системы.

До Python 3.3 существовало много разных типов таких исключений: os.error, socket.error, IOError, WindowsError, select.error и т.д.

Это приводило к тому, что приходилось указывать несколько типов обрабатываемых исключений одновременно:

try:
    do_something()
except (os.error, IOError) as ex:
    pass

Ситуация на самом деле была еще хуже: очень легко забыть указать еще одно нужное исключение, которое может внезапно прилететь. Дело в том что исключения операционной системы часто никак не проявляют себя при разработке. На машине программиста всё работает отлично и он не подозревает о возможных проблемах. Как только программа выходит в production пользователь вдруг ловит что-то неожиданное и программа аварийно завершается. Все опечалены.

Проблема решена в PEP 3151: весь этот зоопарк теперь является псевдонимами для OSError. Т.е. пишите OSError и не ошибетесь (прочие имена оставлены для обратной совместимости и облегчения портирования кода на новую версию).

Давайте рассмотрим ещё один аспект исключений, порожденных операционной системой.

У OSError есть атрибут errno, который содержит код ошибки (список всех возможных символьных констант для ошибок можно посмотреть в модуле errno).

Открываем файл, получаем OSError в ответ. Раньше мы должны были анализировать ex.errno чтобы понять, отчего произошла ошибка: может файла нет на диске, а может нет прав на запись — это разные коды ошибок (ENOENT если файла нет и EACCES или EPERM если нет прав).

Приходилось строить конструкцию вроде следующей:

try:
    f = open(filename)
except OSError as ex:
    if ex.errno == errno.ENOENT:
       handle_file_not_found(filename)
    elif ex.errno in (errno.EACCES, errno.EPERM):
       handle_no_perm(filename)
    else:
       raise  # обязательно выбрасывать не обработанные коды ошибки

Теперь иерархия расширилась. Привожу полный список наследников OSError:

OSError
 +-- BlockingIOError
 +-- ChildProcessError
 +-- ConnectionError
 |    +-- BrokenPipeError
 |    +-- ConnectionAbortedError
 |    +-- ConnectionRefusedError
 |    +-- ConnectionResetError
 +-- FileExistsError
 +-- FileNotFoundError
 +-- InterruptedError
 +-- IsADirectoryError
 +-- NotADirectoryError
 +-- PermissionError
 +-- ProcessLookupError
 +-- TimeoutError

Наш пример можем переписать как:

try:
    f = open(filename)
except FileNotFound as ex:
    handle_file_not_found(filename)
except PermissionError as ex:
    handle_no_perm(filename)

Гораздо проще и понятней, правда? И меньше мест, где программист может ошибиться.

Заключение

Переходите на Python 3.3, если можете. Он хороший и облегчает жизнь.

Новые плюшки в вопросе, касающемся исключений, я показал.

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

пятница, 7 декабря 2012 г.

Python Sprint again

Месяц назад уже был спринт по Питону. Как говориться: всё прошло хорошо, но мало.
Поэтому мы проведем следующие спринт 23 декабря, в воскресенье. Начало в 11.00, место проведения — компания Инсолло (Куреневский пер. 12А, офис 701).

Регистрация
Приглашаются все желающие, но — с регистрацией.
Если вы еще не зарегистрировались и хотите принять участие — заполните гуглоформу.

Инструкция
Чтобы спринт был удачным, к нему нужно подготовиться.
Приходим со своими ноутбуками (думаю, это не проблема).

На ноуте может быть любая система: Linux, Windows, Mac OS X.

Нужно установить компилятор С
 - gcc на Линуксе идет в комплекте
 - на Маке не знаю (там вроде бы можно еще и clang использовать, Питон должен его понимать)
 - для Windows нужно установить Visual Studio, Visual Studio Express доступна для бесплатного скачивания и использования. Для работы нужны обе версии 2008 и 2010.

Также требуется Mercurial HG: http://mercurial.selenic.com/

Работа ведется над CPython repo, установленный из коробки Питон не подходит.

Подробные инструкции на английском языке здесь: http://docs.python.org/devguide/

Делаем:

$ hg clone http://hg.python.org/cpython
$ cd cpython
$ ./configure --with-pydebug
$ make -j4
make install делать не нужно

Далее. Чтобы не тратить время зря советую зарегистрироваться на bugs.python.org и просмотреть заранее список issues. 
Выбирайте помеченные как easy — они как правило действительно простые.

Если у вас уже есть темы, над которыми хотите поработать — добро пожаловать.
Если не сможете определиться — тоже не беда, я подскажу.

Работа выглядит так: 
 - создается issue или берется готовая
 - готовятся исправления
 - делается патч: $ hg diff > issueXXX.diff 
 - он заливается через форму на bugs.python.org Там же добавляете меня (asvetlov) в nosy list
 - я закачиваю патч и применяю его: $ curl http://path-to-patch|patch -p1
 - если всё нормально — делаю push в repo

У Питона сейчас открыты для патчей четыре ветки: 2.7, 3.2, 3.3, default (3.4). 
Новые фичи принимаются в default, баги и правки по документации могут попасть и в остальные три.
Если кто не знает как работать с ветками — я покажу на месте.

Юниттесты запускаются так:
$ make test
или 
$ ./python Lib/test/regrtest.py

Отдельный тест можно запустить
$ ./python Lib/test/test_xxx.py

Перед отсылкой патча настоятельно рекомендую запускать чекер
$ make patchcheck
Эта процедура как минимум убирает лишние пробелы — постоянная головная боль при приеме патчей.

Заключение.
Я буду на месте всё время, помогу вам советом, правкой кода и т.д. Все затруднения быстро порешаем.
Уверен, закроем достаточно ожидающих своего дыр.
Если что непонятно — спрашивайте.