четверг, 20 декабря 2012 г.
понедельник, 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/
Делаем:
$ 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
Эта процедура как минимум убирает лишние пробелы — постоянная головная боль при приеме патчей.
Заключение.
Я буду на месте всё время, помогу вам советом, правкой кода и т.д. Все затруднения быстро порешаем.
Уверен, закроем достаточно ожидающих своего дыр.
пятница, 23 ноября 2012 г.
Learning Advanced Python
Место проведения — Креативний простір "Часопис"Креативний простір "Часопис"
среда, 31 октября 2012 г.
Python Committer
Или, на наши деньги, был включен в команду разработчиков CPython.
С тех пор прошло почти восемь месяцев.
И сегодня я сделал свой двухсотый commit в http://hg.python.org/cpython
Не так уж и мало!
С другой стороны, можно и лучше, да и аппетит приходит именно во время еды. Чем больше вкладываю своих сил в развитие любимого языка программирования, тем больше этот процесс нравится.
Отличная команда, интересный проект, благожелательная атмосфера.
Плюс, работая над развитием языка и стандартной библиотеки, лучше узнаешь как оно устроено, куда развивается и почему именно в этом направлении, заметно повышаешь свой профессиональный уровень.
В общем, мне это занятие очень нравится, буду стараться работать дальше не снижая темпа.
Python Sprints
Что это такое?
Добровольцы собираются вместе и работают над тем, чтобы Питон стал лучше: правят-дописывают документацию, добавляют тесты, чинят баги.
Я могу быть Sprint Lead, показать процесс работы, помочь на месте в случае затруднений, провести review кода и сделать commit.
Детали пока еще не определены. Сначала нужно понять, насколько это вообще интересно и на какое количество людей нужно ориентироваться.
Поэтому, если есть желание принять участие в спринте, убедительно прошу заполнить опрос на гуглоформах.
Если есть вопросы — задавайте.
понедельник, 29 октября 2012 г.
Learning Python
Закончился UA PyCon 2012. Надеюсь, вам понравилось.
Игорь Давыденко на lighting talks сделал презентацию своей идеи по организации курсов изучения Питона.
Я согласился вести advanced курс в рамках этой программы.
Игорь будет вести курс по веб-программированию с использованием популярных инструментов Django и Flask.
Я же хочу дать углубленные знания по тому, как работает веб на примере создания собственного простого сервера, показать приемы замера производительности и оптимизации кода, интеграции питона с С кодом и т.д.
Аудитория — люди, уже имеющие опыт разработки на Питоне и желающие понять что происходит «под капотом».
Курсы состоят из 12 занятий по два часа в неделю каждое.
Час на подачу теоретического материала и час на разбор домашних заданий. Выполнение домашних заданий обязательное.
Стоимость определится после набора группы, ориентировочно 150 грн за занятие.
Вопрос первый: это вообще интересно?
И второй вопрос: о чем бы вам хотелось узнать?
Регистрируйтесь на http://learnpython.in.ua. После набора 10-15 человек начнем обучение.