среда, 31 октября 2012 г.

Python Sprints

У Организаторов UA PyCon есть идея провести как-нибудь спринты по Питону.

Что это такое?

Добровольцы собираются вместе и работают над тем, чтобы Питон стал лучше: правят-дописывают документацию, добавляют тесты, чинят баги.

Я могу быть Sprint Lead, показать процесс работы, помочь на месте в случае затруднений, провести review кода и сделать commit.

Детали пока еще не определены. Сначала нужно понять, насколько это вообще интересно и на какое количество людей нужно ориентироваться.

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

Если есть вопросы — задавайте.

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

Learning Python

Ребята и девчата.

Закончился UA PyCon 2012. Надеюсь, вам понравилось.

Игорь Давыденко на lighting talks сделал презентацию своей идеи по организации курсов изучения Питона.

Я согласился вести advanced курс в рамках этой программы.

Игорь будет вести курс по веб-программированию с использованием популярных инструментов Django и Flask.

Я же хочу дать углубленные знания по тому, как работает веб на примере создания собственного простого сервера, показать приемы замера производительности и оптимизации кода, интеграции питона с С кодом и т.д.
Аудитория — люди, уже имеющие опыт разработки на Питоне и желающие понять что происходит «под капотом».

Курсы состоят из 12 занятий по два часа в неделю каждое.
Час на подачу теоретического материала и час на разбор домашних заданий. Выполнение домашних заданий обязательное.
Стоимость определится после набора группы, ориентировочно 150 грн за занятие.

Вопрос первый: это вообще интересно?
И второй вопрос: о чем бы вам хотелось узнать?

Регистрируйтесь на http://learnpython.in.ua. После набора 10-15 человек начнем обучение.

четверг, 25 октября 2012 г.

Питон: еще раз форматирование

Хочу напомнить об одной занятной штуке, которую сам периодически забываю.

Как переводят дату-время в строку? Обычно так:

>>> from datetime import datetime
>>> d = datetime.now()
>>> d.strftime("%d.%m.%Y")
'25.10.2012'

А на самом деле можно и так:

>>> "{:%d.%m.%Y}".format(d)
'25.10.2012'

Как это работает?

.format разбирает строку формата и определяет параметры форматирования для аргументов. Для первого аргумента, очевидно, это %d.%m.%Y.

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

>>> d.__format__("%d.%m.%Y")
'25.10.2012'

Что это дает? Можно коротко и элегантно записывать сложные шаблоны:

>>> "{} — {:%d.%m.%Y}".format("Today", d)
'Today — 25.10.2012'

То же самое работает и для других классов в datetime и т.д.:

>>> from datetime import date
>>> "{:%A}".format(date.today())
'Thursday'

Более того, никто не мешает определить .__format__ для своего класса, возвращающий строку построенную по заданному шаблону.

Только ради этого стоит переключиться со старого стиля "%s" % "abc" на новый, симпатичный и удобный.

суббота, 6 октября 2012 г.

четверг, 4 октября 2012 г.

Билеты на UA PyCon 2012


Понемногу заканчиваются последние билеты на UA PyCon.
Знаю, что, как и год назад, найдутся особо несознательные граждане, которые будут просить билетик за день до события, соглашаясь на тройную цену стоя и без обеда.
Тем не менее настоятельно рекомендую тем, кто имеет желание посетить конференцию и все еще не приобрел билет — сделать это поскорее.

вторник, 7 августа 2012 г.

Числа в Python 3

Что приходит в голову при словах «числа в Питоне?»

int и float. Если речь идет о Python 2 — еще и упразднённый long. Наверное, вспомнится очень мало где используемый complex.

Я же хочу рассказать о Decimal и Fraction.

Decimal

Просто незаменим, если нужно считать деньги. Представим, что нам нужно работать с гривной (это такая украинская валюта, с точки зрения рассматриваемого вопроса ничем не отличающаяся от рубля, евро или доллара). Сотая часть гривны называется копейкой. Естественно думать, что гривны будут представлены целой частью числа, а копейки — дробной.

Что произойдёт, если для денег мы станем использовать float?

Как я писал в статье: 4 грн 31 коп будут на самом деле иметь внутреннюю запись 4.3099999999999996. Да, при печати всё показывается нормально если у вас Python 2.7+ — но внутри это всё же чуть-чуть иное число!

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

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

Чтобы этого избежать, нужно использовать decimal — который никогда ничего не теряет.

Внутри decimal представлен как знак, набор цифр и положение десятичной точки — т.е. нет никакого округления.

Использование очень простое:

>>> from decimal import Decimal
>>> Decimal("4.31")
Decimal('4.31')
>>> Decimal("4.31") + Decimal("1.10")
Decimal('5.41')

Все стандартные операции над decimal работают так же хорошо, как и с просто числами.

К слову, базы данных как правило имеют встроенную поддержку этого типа, а драйвера DBAPI и ORM вроде Django и SQLAlchemy тоже умеют работать с decimal.

К сожалению, этого базиса недостаточно, а документация, исчерпывающе полная, — не содержит простых ясных инструкций для ленивого программиста.

Пример:

>>> Decimal("1.10") / 3
Decimal('0.3666666666666666666666666667')

Ой! Зачем так много цифр, ведь у нас гривна с копейками?!!

Дело в том, что помимо Decimal есть еще и Context. По умолчанию у него точность в 28 чисел в дробной части, что явно многовато для валюты. Настроим на 2 знака:

>>> from decimal import getcontext
>>> getcontext().prec = 2
>>> Decimal('1.10') / 3
Decimal('0.37')

Уже лучше.

Правила округления тоже задаются контекстом. По умолчанию это ROUND_HALF_UP — округлять вверх, если цифра пять и больше. Как в школе учили. Можно настроить и другой способ — читайте документацию. Еще можно указать, чтобы при разных ситуациях (потеря точности или бесконечность в результате, например) генерировалось исключение а не происходило округление. Кому надо — пусть изучает эту самую документацию, ключевое слово trap.

Вернемся к наиболее распространенным задачам.

Что делать, если часть вычислений нужно проводить с точностью «до копеек», а некоторые (например, то же сведение баланса и подсчет налогов) — до сотых долей копеек?

Наиболее практичный способ — создание своего контекста и применение его в with statement:

>>> from decimal import Context, localcontext
>>> with localcontext(Context(4)):
...     print(repr(Decimal("1.10") / 3))
Decimal('0.3667')

Округление:

>>> Decimal('1.12').quantize(Decimal('0.1'))
Decimal('1.1')
>>> Decimal('1.16').quantize(Decimal('0.1'))
Decimal('1.2')

Внимание! Округлять можно только до той максимальной точности, которая позволена текущим контекстом. Сейчас у нас глобальный контекст имеет точность 2.

>>> getcontext().prec = 2
>>> Decimal('1.10').quantize(Decimal('0.000001'))
Traceback (most recent call last):
...
decimal.InvalidOperation: quantize result has too many digits for current context

Вот и всё.

За рамками статьи осталось многое. Моя цель — дать необходимый минимум знаний, требуемый для работы с денежными единицами. Остальное, повторюсь ещё раз, найдете в документации — она большая и исчерпывающая, содержит описание многих дюжин доступных функций.

И, тем не менее, изложенного достаточно для практически любой повседневной работы.

Важное дополнение. Изначально decimal был написан на чистом питоне. Т.е. корректно считал, но делал это довольно медленно. Не настолько плохо, чтобы отказываться от него (тем более что альтернатив нет) — но часто скорость важна и хотелось бы побыстрее.

В Python 3.3 вошёл ускоритель (подключается автоматически). Автор — Stefan Krah. Большое спасибо этому человеку. Благодаря его труду производительность decimal повысилась настолько, что скорость вычислений стала сопоставима с int и float.

Всем читающим — намёк: переходите на Python 3.3

Fraction

Предназначен для работы с обыкновенными дробями

В школе все учили дроби: «одна треть плюс одна треть равно две трети».

Не все обыкновенные дроби имеют точное конечное представление, укладывающееся в границы float.

Пример:

>>> 7/71*71 == 7
False

А теперь с fraction:

>>> from fractions import Fraction
>>> Fraction(7, 71) * 71 == 7
True

Практическое применение:

С конца мая 2011 я работаю в проекте Апокалипсис. Это онлайновая игра с пошаговой боёвкой. Каждый игрок имеет определённое количество очков на ход. Передвижение на одну клетку — одно очко. Выстрел — скажем, три очка. Ходят все одновременно.

У меня 7 очков движения, у противника 8. Значит, я совершаю свои действия со скоростью 1/7, а противник — 1/8.

Я сдвинулся на одну клетку и выстрелил.

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

float верить нельзя: если событие должно наступить одновременно — ошибка округления наверняка соврёт в ту или другую сторону. А пользователи игры забросают нас, разработчиков, виртуальными гнилыми помидорами.

Fraction считает честно, что к тому же значительно облегчает игровую математику: 1/3 всегда равна «одной трети».

Заключение

Если читатель знал о существовании Fraction и Decimal, использовал их на полную катушку — честь и хвала, я ничего нового не сказал.

Если же, столкнувшись с областью применения этих числовых типов, читатель вспомнит мои рецепты правильного их приготовления — статья свою работу выполнила.

четверг, 19 июля 2012 г.

Питон: времена, даты и временные зоны

В статье Питон: времена, даты и временные зоны я рассказывал, что такое абсолютное и относительное время в терминах Питона.

И упоминал, что сравнение относительного и абсолютного времени выбросит исключение TypeError. В Python 3.3 ситуация изменилась.

Относительное и абсолютное времена всё ещё нельзя сравнивать на упорядоченность (больше или меньше). Сравнение на эквивалентность никогда не срабатывает, при этом ошибки нет.

Пример:

>>> from datetime import datetime, timezone
>>> naive = datetime.now()
>>> aware = datetime.now(timezone.utc)
>>> naive < aware
Traceback (most recent call last):
  ...
TypeError: can't compare offset-naive and offset-aware datetimes
>>> naive == aware
False

Ещё раз подчеркиваю: относительное время никогда не равно абсолютному вне зависимости от того, на какое время суток они указывают.

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