Показаны сообщения с ярлыком python. Показать все сообщения
Показаны сообщения с ярлыком python. Показать все сообщения

понедельник, августа 08, 2016

Как можно участвовать в ICFPC (2016)

«Рядом с этой сокровищницей мысли, — неторопливо думал Васисуалий, — делаешься чище, как-то духовно растешь»

В этом году мы с товарищами (ForNever, Minoru, Hagane, grouzen) опять взялись участвовать в ICFPC. Если кто не в курсе, что это такое — см мой предыдущий пост или серию постов товарища adept.
В этот раз для разнообразия мы не писали AI для игрушки. Организаторы были из Японии, и поэтому мы весь уик-енд... складывали оригами. Кто-то из кода, а кто-то нет.
Задача состояла в том, чтобы из квадратного «листа бумаги» складывать разные (плоские) фигуры. В качестве задачи даётся описание нужной фигуры, а решение — набор складок на квадрате и описание, какие вершины в какие переходят при складывании. Первая сотня фигур была предложена организаторами, а дальше задачи могли отправлять участники, и за отправку задач тоже давали очки.
При этом, на самом деле, это было «не настоящее оригами». Были допустимы решения, невозможные с бумагой — части листа могли проходить друг через друга при складывании. При отправке решений чужих задач можно было даже «рвать бумагу» (при отправке своих задач — нельзя). Момент про разрешено ли разрезание мы уточняли долго и сложно.
Контест в этот раз проходил с утра пятницы по вечер воскресенья в моём часовом поясе.

В пятницу

с утра я «по-быстрому» написал скачивалку задач с сервера и закачивалку решений (на python, просто потому что там было REST API, а у меня уже был опыт использования для этого питоньей библиотеки requests). Ещё я написал парсер предложенного формата задач и рендерилку, которая рисует фигуры из задач в виде svg (на Haskell). Сразу пригодился хаскельный модуль Data.Ratio. Дело в том, что в задачах использовались координаты в виде рациональных чисел, причём размеры числителя и знаменателя не были ограничены. Так что быстро образовалась строчка type Number = Ratio Integer.
Потом я посмотрел на результаты рендера и увидел, что первые семь задач тривиальные (там просто квадрат, сдвинутый или повёрнутый, но не сложенный), решения для них легко написать вручную. Решил и залил.
Потом мы начали думать над задачей (я уже писал, у меня всегда так — сначала код, потом думать).
Когда нет ограничения на разрезание бумаги, задача сводится к тому, чтобы разрезать квадрат на эн многоугольников и сложить из них нужную фигуру. Насколько я знаю, подобные темы широко и глубоко изучены, но я не знаком с конкретными работами и алгоритмами. (в университете был даже какой-то спецкурс по оригами, как разделу геометрии, но я на него не попал). Поэтому я долго и упорно думал в эту сторону, но ничего не придумал.
В чате довольно быстро образовалось несколько идей:
  • Разрезать требуемую фигуру на треугольники и попытаться из таких треугольников сложить квадрат.
  • Какой-нибудь вариант генетических алгоритмов: разрезаем квадрат как попало и складываем как попало, оцениваем скор в соответствии с правилами организаторов.
  • Быстро придумался простой алгоритм для складывания любого достаточно маленького выпуклого многоугольника: пройти по всем его рёбрам и сложить бумагу вдоль них. Если останутся торчащие «хвосты», то опять пройти по рёбрам и подвернуть вдоль них.
  • Кроме силуэта фигуры, в задачах приводился её «скелет» — набор всех рёбер в положении после складывания. При этом, правда, было указание, что это только подсказка, складывать в соответствии с этим скелетом не обязательно. Так вот, появилась идея брать этот скелет и пытаться разворачивать его до квадрата.
Товарищ grouzen произвёл обширные изыскания существующих научных работ на тему оригами и около, и обеспечил нас пейперами для чтения на всю неделю. К сожалению, применить их мы не смогли.
Потом начался как всегда разброд и шатание, каждый стал заниматься чем-то своим.
Я решил начать с написания какого-никакого «фреймворка» для дальнейшей работы — набора функций для разрезания многоугольников, объединения и т.п. Некоторые из геометрических алгоритмов тривиальны (отражение многоугольника относительно прямой), зато другие зубодробительны (объединение произвольных многоугольников). Для таких алгоритмов я стал гуглить существующие реализации, по возможности на Haskell (т.к. парсер задач уже был на Haskell). Нашёл только clipper. Это биндинги к одноимённой библиотеке на C++. Сразу обнаружилась особенность: Clipper работает с целочисленными координатами (видимо, в основном заточен на экранную графику) размером Int64. А у нас рациональные и просто целые очень большие числа. Так что при попытке засунуть в clipper одну из первых тривиальных задач сразу получили: Literal 1267650600228229401496703205376 is out of the Int64 range -9223372036854775808..9223372036854775807. Так что реализацию сложных алгоритмов отложили на потом (по факту объединение многоугольников у нас в итоге и не использовалось). Minoru взялся писать разрезание многоугольника прямой на два (это в любом случае бы понадобилось для складывания, а в clipper этого нет).
Дальше Minoru, Fornever и hagane начали обсуждать вариант с разворачиванием скелета. Я в эту идею особо не вникал, т.к. думал над представлением «фигуры» в процессе её сворачивания и реализацией «сворачивалки» в виде State ЧтоТо ().
К вечеру пятницы у меня был «солвер», который брал многоугольник и один раз сворачивал квадрат вдоль каждой грани этого многоугольника. И рисовал в svg что получилось. То есть он теоретически мог уже решать часть задач. Но при этом он принимал данные не в том виде, и выводил не в том. При этом там было ещё куча неуловленных багов.
Fornever к этому времени перегенерировал все иллюстрации к задачам, устранив баг в рендерилке.

В субботу

с утра я стал было дописывать алгоритм сворачивания выпуклых полигонов, но наткнулся на баги в методе разрезания многоугольника (где-то какое-то деление на ноль). Попытался разобраться в коде этого метода и не смог. Т.к. Minoru сильно в другом часовом поясе, я решился переписать этот код. Стал гуглить как это делается правильно, и нашёл зубодробительные алгоритмы разрезания произвольных многоугольников. После долгих попыток в них разобраться я внезапно понял, что нам нужно разрезать только выпуклые многоугольники (т.к. многоугольники, на которые бумагу могут разделить складки оригами, могут быть только выпуклыми), и быстро написал сравнительно простой метод разрезания выпуклых многоугольников.
Потом я стал писать форматирование решения в соответствии с форматом, прниимаемым сервером. В это время Fornever с Hagene что-то писали на тему «разворачивания скелетов». Hagane ещё и постил задачи для других участников, придуманные «вручную». Потом, чтобы не тратить клетчатую бумагу, Hagane c Fornever стали писать генератор случайных задач.
grouzen предложил написать графическое интерактивное приложение для разворачивания фигур вручную, но до реализации этой идеи как-то ни у кого не дошли руки.
К вечеру субботы у меня был солвер, который решал некоторую часть задач, и я его пачками натравливал на избранные простые задачи. На многих задачах при этом проявлялся баг в геометрических алгоритмах. Тут же написалась решалка тривиальных задач (которые просто квадрат без складываний). Ещё я написал вариант солвера, который складывает вместо требуемой фигуры её выпуклую оболочку — это, очевидно, даёт приближённые решения, но лучше чем ничего.
Потом оказалось, что у нас имеется три реализации геометрии — Minoru и моя на Haskell, Hagane на Racket и Fornever на F#; и две реализации форматирования вывода, обе на Haskell — моя и Hagane, отличающиеся по-моему только названиями функций. На этом месте я взвыл в чатик — мол, нет на нас ПМ-а или хотя бы тимлида.
До позднего вечера я искал баги в реализации геометрии, Fornever с Hagane писали генератор задач, а Minoru писал разворачивание скелетов.
Потом Hagane решил взять на себя функции лида и отправил меня спать. Правда, совсем перед тем как лечь, я нашёл баг в одной строчке (опечатка при копипасте) в реализации разрезания многоугольника, и поправил его, отчего все «волшебные» баги исчезли.

В воскресенье

с утра у меня образовалось несколько идей:
  • сделать параллельный перенос исходного квадрата к месту, где находится искомая фигура — до этого момента все складывания выполнялись «на месте», на единичном квадрате, так что случаи, когда фигура находится где-то сбоку, не обрабатывались.
  • придумать какие-нибудь эвристики, как определить, на какой угол нужно повернуть квадрат перед складыванием — т.к. фигуру «единичный квадрат повёрнутый на 45 градусов», очевидно, нельзя получить из единичного квадрата одними складываниями без поворотов.
  • доделать складывание выпуклых фигур в солвере. К этому моменту у меня делалось всегда 2 итерации обхода многоугольника со складыванием вдоль рёбер, что было избыточно для одних задач и недостаточно для других. Надо было написать проверку — надо ли делать ещё обход.
  • появилась идея по складыванию невыпуклых фигур. Невыпуклый многоугольник с одной невыпуклой вершиной часто можно получить перегибанием выпуклого многоугольника. Поэтому можно поискать способы «развернуть» такие многоугольники в выпуклые, а выпуклые мы складывать уже умеем. Для более хитрых многоугольников можно процедуру разворачивания повторить несколько раз.
Я нарешал скриптом с исправленным солвером пару сотен простых задач и стал реализовывать перечисленные идеи. В итоге где-то к середине дня я реализовал перенос и для самых простых случаев поворот, доделал сворачивание выпуклых фигур, и оставалось только время от времени запускать скачивалку и решалку (ту, которая складывает выпуклую оболочку для всего подряд). Ещё я оставшееся время пробовал всякие улучшения и оптимизации.
В частности, в какой-то момент я вдруг решил попробовать сделать хоть какой-нибудь вариант генетического алгоритма для решения сложных задач. Для этого нужно иметь функцию оценки, а она в данном случае определяется через площади многоугольников. Какое-то время я боролся с функцией нахождения площади из clipper, но то ли я его не так готовил, то ли есть баги в биндингах, то ли в самом clipper — результаты оценки получались настолько неадекватными, что я на это плюнул. Писать собственный расчёт площади времени не осталось.
У моего солвера оставалась существенная проблема: он часто генерировал решения размером в десятки или сотни килобайт, при ограничении в 5000 байт. Вечером стали думать, как уменьшить размер решений. В итоге сделал:
  • если многоугольник не выпуклый и мы всё равно будем делать приблизительное решение — округлить входные координаты до 10-12 знаков после запятой (уменьшает размер дробей на выходе);
  • складывать не по всем подряд рёбрам многоугольника, а сначала только по чётным, потом только по нечётным. Не очень понимаю почему, но на некоторых задачах это помогло существенно.
Вечером Minoru стал пытаться решать сложную задачу номер 101. Там нужно было сложить классического журавлика, и он его стал складывать. Из бумаги. С целью потом перевести эти складки в цифры. Правда, в итоге безуспешно. Но эту задачу вообще решила только одна команда.
Ночью я передал Minoru скрипты для скачивания задач, решения и заливки решений, оставил его их запускать в оставшееся время контеста, и ушёл спать. Leaderboard к этому времени как раз заморозили.

В итоге

к моменту заморозки leaderboard мы оказались на 44м месте (из 293 команд всего, получивших ненулевое количество очков 201). Что есть необычно высокий результат по сравнению с нашими прошлыми попытками.
По моему личному впечатлению, в этот раз задача была заметно проще, чем в предыдущие (я не удивлюсь, если увижу разочарованные отзывы об этом icfpc от «аксакалов» — мол, слишком легко). Но, возможно, это такой perception bias из-за того, что в классической аналитической геометрии с линейной алгеброй худо-бедно разбираюсь, а во всяких AI почти никак не разбираюсь.
У нас, как всегда, были проблемы с организацией и коммуникацией (каждый писал что-то своё, иногда дублируя работу). По-моему, если бы мы физически находились в одном месте, результаты могли бы быть сильно лучше, хотя бы за счёт взаимной мотивации (говорят, психологически сложнее отлынивать, когда видишь как человек за соседним компом сосредоточенно пишет код) :) Как и в прошлые разы, были проблемы из-за того, что члены команды имели сильно разный опыт работы с выбранным языком.
Надеюсь, в следующем году мы как-нибудь сможем смягчить эти проблемы, и результат будет ещё лучше.

воскресенье, января 03, 2010

Текущие проекты

Давно я что-то сюда не писал. Замотался совсем. В частности, несколько неожиданно для себя стал техническим писателем :)

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

Framework

Это фреймворк (не очень высокого уровня, на настоящий момент) для создания web-приложений на Haskell. Страница проекта тут, haddock-документация тут. Проект в значительной мере исследовательский: насколько сложно/просто писать веб-приложения на Haskell? А фреймворки? Какие новые идеи способен привнести Haskell в эту область? Кроме того, изначально я задумывал фреймворк для разработки приложений высокой нагруженности (так что само собой предполагаются всякие кэширования, работа со многими СУБД и мн.др.). Во фреймворке в настоящий момент много чего не хватает (начиная с названия) — нет полноценной ORM, нет генерации произвольных диалектов SQL… Вероятно, как раз в этих областях что-нибудь интересное получится в результате. Кое что [на мой взгляд] оригинальное в фреймворке уже есть. Т.к. Haskell — компилируемый язык, то всё приложение — это один бинарник. Включая шаблоны. Шаблоны пишутся в синтаксисе, похожем на Django-вский (вообще, я многие идеи старался взять из django), при сборке приложения по ним генерируется haskell-исходник и компилируется вместе с приложением. Достоинства и недостатки, собственно, очевидны: нет затрат времени на парсинг шаблонов на каждый запрос (но и затрат сложности на кэширование шаблонов тоже нет), генерация html по шаблонам быстрее, но при изменении шаблонов надо пересобирать приложение (но если речь идёт о большой нагрузке, шаблоны будут меняться редко).

MyPaint

Как несложно догадаться из названия, MyPaint (http://mypaint.intilinux.com) — это программа для рисования (дословно — что-то вроде "моя живопись"; исторически, это название — ссылка на программу paint.exe от microsoft). Программ для рисования сейчас довольно много, в том числе и свободных, и под Linux (конечно, в первую очередь на ум приходит Gimp). Особенность MyPaint — это программа в первую очередь именно для рисования, а не для обработки готовых изображений (собственно, MyPaint даже не умеет таких вещей, как "кроп" или "уровни"; за такими функциями добро пожаловать в тот же Gimp). На самом деле, ближайшие конкуренты MyPaint — это Corel Painter и ArtRage (NB: это не означает, что они идут ноздря-в-ноздрю, просто это программы одного назначения).

Этим летом мои родные-художники дорвались до компьютера, а именно до mypaint, и засыпали меня багрепортами и фичреквестами. В связи с чем я начал разрабатывать свою ветку mypaint. Сейчас мои наработки будут постепенно вливаться в основную ветку.

Коротко изменения — i18n и перевод на русский (сейчас уже в основной ветке, вместе с переводами на французский, норвежский, шведский, упрощённый китайский и др.); палитра; что-то наподобие масок; диалог слоёв; группировка кистей; поддержка наклона пера. Подробнее на вики mypaint, там же рядом бурное обсуждение интерфейса.

Надеюсь в ближайшее время сделать отдельную статью про MyPaint. А пока, «с первого и по тринадцатое», собираюсь поплотнее заняться разработкой с целью сделать мои нововведения пригодными для вливания в основную ветку — релиз планируется сразу после этого мержа.

Todos

Ещё один проект без нормального названия :) Это простецкий TODO-менеджер на Haskell. Собственно, сами TODO пишутся в любом текстовом редакторе в plain-text файлике (желательно, с названием TODO) в простецком формате:

[spaces]status [TAGS] title (depends) description

где [spaces] — отступ пробелами, status — состояние задачи, [TAGS] — список тегов в квадратных скобках через пробел, title — заголовок или описание, (depends) — список зависимостей (заголовков других записей) в скобках через запятую, description — описание. Все поля кроме статуса и заголовка необязательны. Отступами определяется подчинённость записей, благодаря зависимостям (которые в скобках) структура может быть не только деревом, но произвольным графом (даже циклическим). Собираюсь приделать поддержку дат (чтобы, например, можно было отобрать дела, запланированные на завтра).

Сама программа только отбирает записи из файла по условиям, заданным в командной строке (см. вывод todos --help). Фильтровать можно по любому из полей. Есть «сложные» запросы, вида «задачи с тегом BUG и статусом отличным от FIXED», правда парсер таких запросов работает пока не идеально.

Смотреть/брать код здесь: http://gitorious.org/todos/. Компилируется GHC 6.10.4. Из зависимостей — пакет text-regex-pcre.

вторник, декабря 23, 2008

Все форматы документа из одного исходника: asciidoc сотоварищи

Я уже давно использую asciidoc для написания сколько-нибудь больших текстов. Почти все статьи в этом блоге, включая эту, подготовлены с помощью Asciidoc.

Asciidoc - это транслятор простейшего языка разметки текста в любой другой язык разметки. Разметка asciidoc очень простая, практически вы пишете plain text, только выделяете заголовки знаками = в начале строки, полужирный текст - *звёздочками*, курсив - 'кавычками', итд. Абзацы разделяются пустой строкой. А на выходе может быть всё что угодно, это зависит от так называемого backend-a, поведение которого описывается в конфиге. В поставке доступны бэкенды для xhtml, html4 и docbook. Docbook, в свою очередь, теоретически можно отконвертировать во что угодно.

На днях я готовил доклад для одного семинара, и мне хотелось получить его сразу в нескольких форматах: html и pdf, как минимум. И ещё бы надо к нему презентацию… И хорошо бы план доклада. И, конечно, не хочется для каждого формата готовить текст.

HTML (точнее, xhtml 1.1) делается с помощью asciidoc. Все остальные форматы, теоретически, можно получить из docbook, который можно получить с помощью asciidoc. Только вот на практике мне так и не удалось за полдня заставить ни один из конверторов docbook нормально работать с русскими буквами. Также в комплекте asciidoc есть экспериментальный бэкенд latex, но он как-то странно работает с кусками кода, которые мне нужно поместить в tex-файл в неизменном виде (речь идёт о формулах): половина формул куда-то проглатываются.

Кроме всего прочего, мне нужно в доклад включать фрагменты диалога с консольными программами (в данном случае - с maxima и с R). Так как в ходе подготовки доклада что-то может меняться, неохота каждый раз делать copy-paste из консоли. Надо бы, чтобы в исходник вставлять только запросы к программам - а вызываются программы и вставляется вывод пусть автоматически.

В общем, в итоге я написал скрипт lmaxima.py, который делает следующее: читает входной файл, и копирует его в выходной. Если встречает строку вида "program>> команды", то по пайпу передаёт эти команды указанной программе, и её ответ вставляет в выходной файл. Если встречает строку вида "program|tex>> команды" - то указанные команды оборачивает в функцию tex(). Таким образом, lmaxima.py работает как препроцессор для asciidoc. Одна из тонкостей состоит в том, как вставлять в документ формулы, которые выдаёт maxima. Если выводить надо в html, то формулы пропускаются через tex, и в выходной файл вставляется картинка (строка image:chtoto.png[]). Если же выводить надо в pdf, то lmaxima указывается ключ -i, и в выходной файл вставляется непосредственно tex-код.

Т.к. latex-бэкенд к asciidoc работает странно, пришлось писать свой конвертер из подмножества asciidoc-разметки в tex (благо, основная часть разметки asciidoc очень простая). Называется он у меня vsml.py. Заодно vsml.py умеет следующее:

  • С ключом -c - добавляет в документ оглавление (latex-овская команда \tableofcontents),

  • с ключом -p - "выдирает" из исходника только заголовки, и составляет содержание документа (план доклада, в моём случае),

  • с ключом -b - создаёт исходник для презентации (класс beamer); в презентацию попадают заголовки и картинки.

vsml понимает ещё и некоторые "надстройки" над синтаксисом asciidoc. Так, с помощью строчек "//{" и "//}" (asciidoc их воспринимает как комментарии) можно создавать вложенные куски текста. По умолчанию они выводятся как обычно, однако vsml.py можно задать ключ -l с числовым параметром, и он будет выводить только текст с "уровнем вложенности" не больше заданного; это позволяет оформлять более и менее необязательные части текста, и из одного исходника создавать документы разной степени подробности. А с помощью строчки вида "//.Тут заголовок" можно создавать (под)заголовки, которые не будут видны нигде, кроме презентации.

Конечно, вручную писать все эти команды с ключами каждый раз долго, поэтому я написал небольшой Makefile:

all: report.pdf report.html presentation.pdf plan.pdf
clean:
rm report.pdf report.html presentation.pdf
rm presentation.tex report.asciidoc report.vsml
rm plan.tex plan.pdf
plan.pdf: plan.tex
pdflatex $<
plan.tex: report.vsml
vsml.py -p < $< > $@
report.pdf: report.tex
pdflatex $<
presentation.pdf: presentation.tex
pdflatex $<
report.html: report.asciidoc
asciidoc $<
report.asciidoc: math-report
lmaxima.py $< $@
presentation.tex: report.vsml
vsml.py -b < $< > $@
report.tex: report.vsml
vsml.py < $< > $@
report.vsml: math-report
lmaxima.py -i $< $@


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

воскресенье, ноября 23, 2008

Создание собственных виджетов в PyGTK с помощью cairo

Свободная библиотека Gtk, как известно, не отличается очень большим выбором виджетов. Но никто не мешает создавать свои собственные.

Gtk, как известно, построена на принципах ООП, что хорошо ложится на объектную модель Python. В данном случае это означает, что наследование виджетов естественным образом соответствует наследованию классов в Питоне. Так, создав класс-потомок gtk.VBox, мы получим виджет со всеми свойствами VBox, и сможем добавлять в него нужную функциональность.

Покажу простейший пример. Пусть мы хотим создать виджет, выглядящий как комбинация gtk.Label и gtk.Entry, т.е. поле для ввода сразу с подписью. Чтобы сделать такое непосредственно средствами gtk, нужно создать gtk.HBox, а в него поместить Label и Entry. Т.е. HBox окажется родительским виджетом для всей конструкции. Вот от него и будем наследоваться:

class LabeledEntry(gtk.HBox):

Но наш виджет довольно сильно отличается от простого HBox, поэтому нужно переопределить инициализатор:


  def __init__(self,label=None):
gtk.HBox.__init__(self) # Вызываем инициализатор родительского класса
self.label = gtk.Label(label) # Создаём текстовую метку с нужной подписью
self.entry = gtk.Entry() # И поле для ввода текста
self.pack_start(self.label, expand=False) # Добавляем label в создаваемый виджет
self.pack_start(self.entry, expand=True) # Поле для ввода - туда же

Теперь можно дописывать методы по собственному усмотрению. Например, логично было бы видеть методы set_text и get_text:

  def get_text(self):
return self.entry.get_text()
  def set_text(self,text):
self.entry.set_text(text)

При желании можно добавить, например, get_label и set_label. Пример использования нашего виджета:

...
entry = LabeledEntry("Enter some text")
...

Таким образом, наследуясь от HBox или VBox, можно создавать виджеты, состоящие из нескольких готовых. Но иногда нужны виджеты, внешне не похожие ни на один из стандартных. И вот тогда выручает то, что все виджеты gtk отрисовываются с помощью Cairo, который имеет весьма простой API.

API этот имеет много общего со многими другими рисовальными API. Прежде всего, нужно получить контекст Cairo - объект, содержащий состояние изображения. Далее для собственно рисования вызываются методы этого объекта. Наиболее часто используемые:

  • cr.move_to(x,y) - переместить графический указатель в нужную точку холста,

  • cr.line_to(x,y) - провести линию от положения указателя до данной точки (указатель сдвинется в указанную точку),

  • cr.path_close() - делает текущую линию замкнутой,

  • cr.rectangle(x,y,w,h) - рисует прямоугольник; задаются координаты левого верхнего угла и размеры,

  • cr.set_source_rgb(r,g,b) - выбрать цвет для рисования; компоненты r,g,b измеряются от 0 до 1,

  • cr.stroke() - нарисовать контур текущей линии (выбранным цветом),

  • cr.fill() - закрасить текущую линию.

Координаты измеряются как обычно - от левого верхнего угла вправо и вниз, в пикселах.

Пусть нам, скажем, нужен виджет, который будет отображать простейшие линейные диаграммы. Должна быть возможность добавлять в него данные, а он должен соответственно перерисовывать диаграмму. Такие виджеты удобнее всего наследовать от gtk.DrawingArea:

  class Diagram(gtk.DrawingArea):

Сам виджет DrawingArea выглядит как белый прямоугольник. И на нём, в соответствии с названием, можно рисовать. Пока сделаем инициализацию нашего виджета:

  def __init__(self,max=10,color=(0.8,0.8,0.6)):
gtk.DrawingArea.__init__(self)
self.data = [1] # Это будут данные, отображаемые виджетом
self.max = max # Сколько максимум данных будет рисовать виджет
self.color = color # Цвет диаграммы
# Вот это, можно сказать, самое главное: привязываем рисующую процедуру к событию перерисовки виджета
self.connect('expose-event', self.on_expose)

Определяем собственно метод, который будет отрисовывать виджет:

  def on_expose(self, widget, event):

В аргументе widget передаётся сам виджет. Первое, что нам от него нужно - это размеры и положение:

    x,y, width,height,_ = widget.window.get_geometry()

Кроме того, нам понадобится контекст Cairo:

    cr = widget.window.cairo_create()

Вычислим некоторые размеры:

    xpad = 0.03*self.width           # Поля по горизонтали
ypad = 0.07*self.height # И по вертикали
w = float(self.width-2*xpad) # Ширина 'рабочей' части виджета
h = float(self.height-2*ypad) # и высота
M = max(self.data) # Максимум данных - он нужен, чтобы выставить масштаб по оси Y
n = len(self.data) # Количество данных
    cr.rectangle(0,0,self.width,self.height)   # Обозначаем прямоугольник, закрывающий весь виджет
cr.set_source_rgb(1,1,1) # Выбираем белый цвет
cr.fill() # Закрашиваем наш прямоугольник - это будет фон
    cr.move_to(xpad, ypad+h-h*float(self.data[0])/M)  # Ставим указатель в верхний левый угол будущей диаграммы
for x,y in enumerate(self.data[1:]): # Пробегаемся по всем данным
cr.line_to(xpad+w*float(x+1)/(n-1), ypad+h-h*float(y)/M) # Проводим очередной отрезок ломанной
cr.line_to(xpad+w, ypad+h) # Проводим правую границу диаграммы
cr.line_to(xpad,ypad+h) # Теперь нижнюю границу
cr.close_path() # Замыкаем ломанную - это проведёт левую границу диаграммы
cr.set_source_rgb(*self.color) # Выбираем цвет
cr.fill() # Закрашиваем ломанную

Этот метод будет вызываться каждый раз, когда нужно перерисовать виджет. Конечно, стоит иметь ввиду, что если он будет выполняться долго - перерисовка виджета будет тормозить. Так что вычислений и циклов в нём должно быть минимум. Всё, что можно, следует вычислять заранее, или кэшировать.

Ну и допишем метод для добавления данных в диаграмму:

def accept(self,n):
if len(self.data) == self.max:
del self.data[0] # Если данных слишком много - забываем самое старое значение
self.data.append(float(n)) # Добавляем число в список
self.queue_draw() # Этот вызов заставит виджет перерисоваться, т.е. лишний раз вызовет on_expose().

Пример использования:

...
dg = Diagram(max=20)
...
dg.accept(10)
dg.accept(20)
...

воскресенье, июня 15, 2008

Всякое разное: Drupal, Git, Django...

Давно я что-то не писал постов… За это время в голове много чего накопилось, теперь попытаюсь изложить.

Drupal

Вот, за прошедшее время успел более-менее поглубже изучить эту штуку. Кто не слышал - это CMS, а точнее, CMF (content management framework) такой. Я когда-то давно на него смотрел и даже сайты на нем делал, но по принципу "поставил движок, настроил - сдал". Недавно вот чуть посложнее сайты делать пришлось, даже модули к нему писал. Вобщем, drupal хоть и на PHP, но мне понравился ;) Чем именно:

  • посмотрел код - на редкость для php-приложений чистый и понятный;

  • система прав на основе ролей;

  • куча модулей, под каждую задачу, прямо unix-way;

  • эти модули на удивление хорошо дружат меджу собой, конфликтов почти нет;

  • еще понравилась идея связей между модулями за счет хуков (hooks) - для меня новая. Хук - это просто функция, вызов которой можно перехватить из другого модуля и сделать в дополнение что-то своё. Каждый модуль может создавать хуки и перехватывать существующие. В результате получается, что архитектура приложения (порядок связей между модулями) может меняться при изменении набора модулей.

  • удивительно вменяемое русскоязычное сообщество на drupal.ru.

Конечно, есть у него и проблемы - например, с производительностью не всегда всё отлично, в основном из-за того, что большое количество модулей порождают большое количество запросов к БД.

Git

До недавнего времени я весь свой программный код хранил в svn. Впрочем, это в основном были совсем небольшие программки. А тут появился на горизонте некий более крупный проект, который предполагалось разрабатывать командой (по крайней мере - не в одиночку). И именно в этот момент появился пост Ивана Сагалаева - жалоба на проблемы с merge в subversion. Мне почему-то сразу расхотелось использовать svn :) Стал смотреть на альтернативы, благо их довольно много. Git приглянулся во-первых своей распределенностью со всеми вытекающими, во-вторых - это мейнстрим (среди dvcs), что ни говори ;). Вобщем, стал изучать. Больше всего понравилось:

  • репозиторий создается фактически одной командой (git init), второй командой в него добавляются файлы (git add .), ну и третьей делается первый коммит (git commit). Получается быстрее, чем с subversion, так что имеет смысл использовать его даже для программы из пары файликов или конфига.

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

  • графическая морда, gitk, хорошо показывает ветви, коммиты и всё остальное.

Вобщем, перевел я весь свой код на git, благо тулза для импорта из svn в git есть (git-svn, так и называется).

Django

Эту вещь я когда-то уже тоже изучал, и даже что-то писал, но уже даже не помню, что писал :) Блог, кажется, по традиции :) Сейчас вот изучаю заново (в придачу, с предыдущего подхода многое в django успело поменяться). Кто еще про django не слышал (а тут такие есть? :D) - это фреймворк (т.е., по большому счету, набор библиотек + определенный способ их использования) для создания веб-приложений на python.

Сейчас активно пишу на django что-то типа groupware, назвал пока незамысловато - Projects (надо будет еще придумать подходящее название). Описание тут: http://iportnov.ru/projects/projects.

Еще до этой штуки стал писать библиотечку, которую пока назвал HMS (hooks modules system). Это, собственно, эксперимент: питоновский пакет, с помощью которого в любое питон-приложение можно добавить систему модулей, работающую "примерно как в друпале" (см. выше, про хуки). По-моему, оно довольно красиво смотрится в связке с Django. Сейчас у меня эта HMS входит в состав Projects, хотя пока и мало используется там (я пока что в ядре не все функции написал, не до модулей пока).

Еще в комплекте с Projects у меня есть pygit - веб-интерфейс к Git, написанный на django. При желании его можно почти безболезненно выдернуть из Projects и использовать в других проектах. Для связи с Git использует пакет GitPython. Вот только в этом gitpython есть пока баги, и тормозит он в некоторых местах… Ну, баги, надеюсь, скоро починят, а тормоза можно и стороной обойти.

среда, марта 19, 2008

Доклад по Python: часть III

9. Функциональное программирование.

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

Однако некоторые элементы ФП могут быть использованы в Питоне очень эффективно. В частности, выше мы видели примеры применения конструкции lambda. Эта конструкция создает анонимную функцию, а точнее - замыкание (то есть создаваемая функция "запоминает" значения внешних переменных в момент создания). Классический пример использования замыканий:


def add(x,y):
return x+y

inc = lambda x: add(x,1)

def incrementer(n):
return lambda x: add(x,n)

inc(3) # выдаст 4

f = incrementer(3)

f(5) # выдаст 8.

Другие типичные конструкции, позаимствованные из функциональных языков - это стандартные функции map,filter,zip и специальная форма, называемая списочным сокращением (list comprehension).

Функция map(f,list) возвращает список значений, полученный применением функции f к каждому элементу списка list. Пример:

>>> def sqr(x):
... return x*x
...
>>> map(sqr,[1,2,3])
[1, 4, 9]


Функция filter(p,list) возвращает список из только тех элементов списка list, для которых функция p возвращает истину. Пример:


>>> def even(n):
... return n%2 == 0
...
>>> filter(even,[1,2,3,4,5,6])
[2, 4, 6]

Функция zip принимает два (или больше) списка и возвращает список пар (кортежей), составленных из соответствующих элементов списков:

>>> zip([1,2,3,4],["A","B","C","D"],['x','y','z','t'])
[(1, 'A', 'x'), (2, 'B', 'y'), (3, 'C', 'z'), (4, 'D', 't')]

List comprehensions позволяют одной строкой создать списки по какому-нибудь правилу:

>>> [x*x for x in [1,2,3,4,5]]
[1, 4, 9, 16, 25]

Можно тут же отбирать нужные элементы списка:

>>> [x*x for x in [1,2,3,4,5] if even(x)]
[4, 16]

Можно перебирать одновременно несколько последовательностей:

>>> l = [1,2,3,4,5]
>>> [x*y for x in l for y in l]
[1, 2, 3, 4, 5, 2, 4, 6, 8, 10, 3, 6, 9, 12, 15, 4, 8, 12, 16, 20, 5, 10, 15, 20, 25]

10. Декораторы.

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

Применяются декораторы так:

@decor
def f(x,y):
...

и это эквивалентно такой записи:

def f(x,y):
...
f = decor(f)

Классический пример использования декораторов - это отслеживание вызовов функции:
def trace(func):
name = func.func_name # имя переданной функции
def wrapper(*args,**kwargs): # создаем новую функцию
print "Function called: %s(%s,%s)" % (name,args,kwargs)
result = func(*args,**kwargs) # вызываем исходную функцию
print "Function %s returned %s" % (name,result)
return result
return wrapper # возвращаем созданную функцию

@trace
def f(x,y):
return x*y+2

Обычно декоратор выполняет какую-то работу дополнительно к тому, что делает сама функция. Например, в веб-фреймворке Django есть декоратор login_required - он проверяет, что пользователь уже авторизовался, и только в этом случае вызывает исходную функцию.

Таким образом, декораторы в Питоне обеспечивают возможность аспектно-ориентированного программирования.

Другой пример - декоратор может вызывать исходную функцию только в том случае, если включен режим отладки. Или в режиме выполнения юнит-тестов вызывать функцию-заглушку.

Собственно функция-декоратор должна принимать только один аргумент - исходную функцию. Но можно писать функции, которые возвращают декораторы:

def print_on_call(text):
def decorator(func):
def wrapper(*args,**kwargs):
print ">> "+text
return func(*args,**kwargs)
return wrapper
return decorator

@print_on_call("F called!")
def f(x):
...

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

def delegate(cls):
def decorator(meth):
name = meth.func_name
def wrapper(*args,**kwargs):
m = object.__getattribute__(cls,name)
result = m(*args,**kwargs)
return result
return wrapper
return decorator

class A(object):
x = 0
def two(self,z):
r = self.x*z
self.x = z
return r

class B(object):
x = 3

@delegate(A)
def two(self,z):
pass

b = B()
print b.two(5) # Выводит 15

11. Дескрипторы

Дескриптор — это класс с определенными методами __get__() и __set__(). Предполагается, что __get__() возвращает значение экземпляра, а __set__() — соответственно, устанавлнивает.

Особенность дескрипторов состоит в том, что они нормально работают только как атрибуты классов.

В Питоне есть стандартный класс-дескриптор property, конструирующий свойство объекта из getter-а и setter-а. Типичный пример использования property:


class A(object):
def getx(self):
print "Getter called."
return self._x

def setx(self,value):
print "Setting .x to %s" % value
self._x = value

x = property(getx,setx)
Пользовательские классы-дескрипторы могут выполнять более сложную работу: например, можно хранить количество присваиваний (или более сложное состояние) внутри класса-дескриптора.

Таким образом, дескрипторы - это значительное обобщение свойств (properties), имеющихся в C#.

12. Метаклассы

В Питоне всё есть объект. В том числе, классы - это тоже объекты.
Самые часто используемые объекты — это экземпляры классов. А классы, в свою очередь, являются экземплярами метаклассов.

Можно взглянуть на это несколько с другой стороны: класс — это шаблон для создания экземпляров класса. А метакласс — это шаблон для создания классов.

По умолчанию используется стандартный метакласс type. Однако, наследуясь от него, можно создавать свои метаклассы.

Чаще всего метаклассы используются, когда нужно добавить некоторые атрибуты (методы) во все создаваемые классы.

>>> class Meta(type):
... def whoami(cls):
... print "I'm a", cls.__name__
...
>>> class A(object):
... __metaclass__ = Meta # Указываем используемый метакласс
... def do_something(self):
... print "Doing something."
...
>>> a = A()
>>> A.whoami()
I'm a A
>>> a.do_something()
Doing something.

Также можно переопределять процесс создания классов:

>>> class Meta2(type):
... def __new__(cls,name,bases,dct): #Конструктор
... print "Creating class",name
... return type.__new__(cls,name,bases,dct)
... def __init__(cls,name,bases,dct): #Инициализатор
... print "Init'ing class", name
... super(Meta2,cls).__init__(name,bases,dct)
... cls.x = 25 # Добавляем атрибуты к классу
... cls.y = 30
...
>>> class B(object):
... __metaclass__ = Meta2
... def f(self):
... print "Calling F."
...
Creating class B
Init'ing class B
>>> b = B()
>>> b.x
25
>>> b.f()
Calling F.

Таким образом, метаклассы позволяют часть логики каждого класса вынести за пределы самого класса. Это еще одна возможность для аспектно-ориентированного подхода.

Доклад по Python: часть II

3. Объекты.

Питон - это объектно-ориентированный язык. Среди всего прочего, это означает: всё есть объект.
В C++, на примере которого (к сожалению) обычно обучают объектно-ориентированному программированию, объектами являются только экземпляры классов. Числа, например, объектами не являются.
В Java значения атомарных типов тоже являются объектами, но несколько искуственно: для них создаются так называемые boxed значения, то есть для каждого (например) числа создается экземпляр спецциального класса, содержащий это число.
В Питоне же всё является объектом. Например: экземпляры классов, собственно классы, типы, атомарные объекты (числа и строки), а также функци. Пример с числом:

>>> a = 1
>>> a.__str__() # этот метод дает строковое представление объекта
'1'

Функции принимают в качестве аргументов объекты и возвращают объекты. Например, функция может принимать функцию в качестве аргумента и возвращать класс, или принимать строку и возвращать функцию.

Рассмотрим помаленьку все традиционные принципы ООП.


4. Инкапсуляция.

Инкапсуляция подразумевает: алгоритмы работы с данными хранятся вместе с данными. Для атомарных значений мы это уже видели в предыдущем разделе (a.__str__()). Для экземпляров пользовательских классов это реализуется образом, очень похожим на C++ или Java:


class A(object): # object - класс, стоящий в вершине иерархии наследования
x = 0
y = 0

def move(self,x,y):
self.x = x
self.y = y

a = A()
a.move(2,3)


Здесь можно увидеть, что в Питоне указатель на экземпляр класса передается в методы явным образом, как первый аргумент (из стандарта С++ известно, что там это реализовано так же, только этот первый аргумент явно не выписывается). Здесь видно следование первому принципу философии Питона: явное лучше неявного.

Главное в инкапсуляции, как мы увидим позже - не надо путать инкапсуляцию и сокрытие данных.


5. Наследование.

Наследование подразумевает возможность создания классов объектов, ведущих себя почти также, как "родительский" класс, но "немного по-другому". В простейшем случае реализация наследования в Питоне похожа на C++:


class A(object):
x = 0

class B(A):
y = 1


Возможно и множественное наследование: class A(B,C,D):...

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

Эта проблема решается различными способами. В С++ для этого используется множественное наследование. В Java - интерфейсы. В Ruby - mix-ins (примеси).

В Питоне используется концепция, называемая Duck Typing: «Если ЭТО ходит, как утка, и крякает, как утка - значит, это утка». То есть, если у объекта есть все нужные функции свойства и методы, то он подходит в качестве аргумента. Например, в функцию


def f(x):
return x.get_value()


можно передавать объект любого типа, лишь бы у него был метод get_value().

Еще одна типичная проблема, возникающая в связи с множественным наследованием - не всегда очевидно, в каком порядке будут просматриваться родительские классы в поисках нужного свойства или метода. В Питоне для упрощения этой проблемы у каждого класса есть свойство __mro__ (method resolution order):

>>> class A(object): pass
...
>>> class B(object):
... x = 0
...
>>> class C(A,B):
... z = 3
...
>>> C.__mro__
(<class 'C'>, <class 'A'>, <class 'B'>, <type 'object'>)


6. Полиморфизм.

Полиморфизм - это способность функции работать с аргументами разных типов.
В C++ и Java полиморфизм тесно связан с наследованием. Например, в C++, если объявлено, что функция f принимает экземпляр класса A, то она может принимать экземпляр любого класса, унаследованного от A. В Java это поведение расширено: за счет интерфейсов (interfaces) есть возможность передавать в функцию экземпляры классов, не связанных "генетически" (но реализующих один интерфейс).

В Питоне полиморфизм реализован за счет Duck Typing: любая функция может принимать объекты любого типа, но если она попытается использовать свойства, которых у данного объекта нет, возникнет исключение (exception) (функция может перехватить его в конструкции try...except и сделать в этом случае что-то другое). За счет этого, например, функция, работающая с файлами, может принимать в качестве аргумента имя файла или дескриптор открытого файла - и действовать по обстоятельствам.

Таким образом, в Питоне (как и было задумано создателями парадигмы ООП) полиморфизм и наследование - совершенно ортогональные принципы.


7. Интроспекция.

Про этот принцип регулярно забывают, когда рассказывают об ООП на примере С++, а между тем это один из основополагающих принципов.

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

У каждого объекта есть некоторое количество атрибутов. Атрибуты, имена которых, начинаются с подчеркивания, считаются приватными (private), хотя это и не влияет на область видимости - это только соглашение. "Более приватными" являются атрибуты, имена которых начинаются с двух подчеркиваний - снаружи они винды только как __имя-объекта__имя-атрибута__. Атрибуты, начинающиеся с двух подчеркиваний и заканчивающиеся двумя подчеркиваниями, имеют специальный смысл.

Список всех атрибутов любого объекта можно получить с помощью встроенной функции dir:

>>> a = 1
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__str__', '__sub__', '__truediv__', '__xor__']

Объект, имеющий атрибут __call__, можно вызывать как функцию (собственно, функции в Питоне отличаются от остальных объектов только наличием этого атрибута). Для проверки, можно ли использовать объект как функцию, используется стандартная функция callable(f). Таким образом, методы объекта - это атрибуты, которые можно вызывать.

У функций и классов есть атрибут __doc__, содержащий так называемый docstring - строку документации. При описании функции она пишется на отдельной строке после def, при описании класса - после class. Стандартная функция help() выдает информацию о любом объекте.

Атрибут __name__ любого объекта содержит его имя. У экземпляров классов атрибут __class__ содержит ссылку на класс этого объекта.

Стандартная функция type() возвращает тип объекта (тип - это тоже объект).

С помощью функции isinstance(obj,cls) можно выяснить, является ли объект экземпляром данного класса (или одного из дочерних классов). А функция issubclass(cls1,cls2) выясняет, является ли cls1 потомком cls2.

Модуль inspect, входящий в стандартную поставку Питона, содержит некоторые дополнительные возможности интроспекции. В частности, функция inspect.getargspec(func) сообщает, сколько и каких аргументов ожидает получить функция.


8. Динамизм.

Этот принцип не был сформулирован как один из основных для ООП, однако референсная реализация ООП - Smalltalk - этим свойством обладает.

Речь идет о том, что свойства объекта (включая даже его тип) могут изменяться во время исполнения. Пример:

>>> class A(object):
... pass

>>> a = A()
>>> a.x = 25 # создаем новый атрибут объекта
>>> b = A() # другой экземпляр того же класса
>>> print b.x # вызовет исключение: у объекта b нет атрибута x
>>> b.y = 30 # создаем другой атрибут
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'x']
>>> dir(b)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'y']

Можно создавать "на ходу" даже методы класса:

>>> A.method = lambda self,s: "<%s %s>" % (s,self.x)
>>> c = A()
>>> c.x = 25
>>> c.method("Text")
'<Text 25>'

Доклад по Python: часть I

1. Что такое?..

Python - это интерпретируемый алгоритмический объектно-ориентированный язык со строгой динамической типизацией, полиморфизм в нем реализован в виде Duck Typing.

Трансляция питона организована очень схожим с Java образом. Именно, исходник компилируется в байт-код, а затем этот байт-код исполняется. Это сходство настолько велико, что существует реализация Питона (Jython), генерирующая Java байт-код для исполнения виртуальной машиной Java. Различие состоит в политике, когда записывать байт-код на диск. Напомню, для Java традиционный способ запустить только что написанную программу такой: запускаем компилятор, подсовывая ему исходник - он генерирует байт-код и записывает его в файл. Затем запускаем виртуальную машину, подсовывая ей байт-код - и она его исполняет.
Питон же обычно не записывает байт-код на диск. В простейшем случае запуск программы происходит так: мы "скармливаем" исходник интерпретатору; он генерирует байт-код, но оставляет его в памяти, а затем передает виртуальной машине (являющейся частью интерпретатора). Это ускоряет запуск программы за счет отсутствия необходимости записывать байт-код на диск.
Однако, при загрузке (импорте) модулей Питон пытается сохранить байт-код, чтобы в следующий раз загрузка модуля происходила быстрее. Есть возможность и саму программу записать в виде байт-кода. Интерпретатору Питона можно подсовывать не только исходники, но и байт-код - он загрузится быстрее.
За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).
И именно из-за этого сходства Питон обычно сравнивают именно с Явой.

Еще одно сходство с Явой (и многими другими интерпретируемыми и даже некоторыми компилируемыми языками) - это автоматическое управление памятью. В Питоне нет new[] и delete[], память отводится и освобождается автоматически. Алгоритм сборки мусора как бы "двухслойный": во-первых, сам интерпретатор реализует reference counting (удаляя объекты, на которые никто не ссылается), и во-вторых, есть время от времени запускаемый garbage collector, работающий по более замысловатым, но более быстрым и надежным алгоритмам (например, reference counting не удалит два объекта, ссылающихся друг на друга, даже если на них больше никто не ссылается).

Удобным свойством интерпретатора Питона является наличие REPL (read-eval-print loop), то есть возможности вводить языковые конструкции с консоли и тут же получать результат. Это часто используется для проверки каких-нибудь идей или для отладки.

1.1. Синтаксис.

В самых общих чертах синтаксис Питона напоминает С или Паскаль. Операторы записываются обычно по одному на строку. Присваивание записывается как в С, знаком =. Но при этом присваивание не возвращает значения, поэтому его нельзя использовать в условии. Для сравнений используются обычные для С знаки ==, !=, >,<.>=,<=. Небольшое отличие состоит в том, что Питон понимает "двойные" сравнения в "математическом" смысле, т.е. 0 < x < 10 понимается как 0 < x and x < 10. Основные конструкции проще показать на примерах:

if x < 0:
print "Yes"
elif x==0:
print "X is 0"
else:
print "No"

for i in [1,2,3]:
print i
else:
print "Loop was not break'ed"

while x>0:
print x
x -= 1

try:
z = x/y
except ZeroDivisionError,e:
print e
else:
print "Divided sucsessfully."
finally:
print "Some cleanup."

def fun(x,y):
return x+y

class Child(Parent):
x = 0
def method(self,x,y):
return self.x - x + y

f = open("abc.txt")
for line in f:
print "<%s>" % line
f.close()

В последнем примере показана замена функции sprintf: "строка" % (данные). В проектируемой сейчас версии Python3000 этот способ исчезнет, а вместо него появится другой: "Value {0}, Value {1}".format("one", 2).


1.2. Типы данных.

В Питоне выделяют атомарные и структурные (или ссылочные) типы данных. К атомарным типам относятся числа и строки. Структурные типы - это списки, кортежи (tuples), словари, функции, классы и экземпляры классов.

Данные некоторых типов (а именно - кортежи и строки) являются неизменяемыми.

Списки записываются так: [1, "one", 25]. Список может содержать любое количество объектов любого типа. Обращение к элементу списка - по индексу: a[2] (элементы нумеруются с нуля). Добавление элементов в список можно делать так:

a = a + [25]

или так (предпочтительней, т.к. быстрее):

a.append(25).

Кортежи записываются как значения через запятую: a,b,c. Часто для ясности кортежи приходится записывать в скобках: (a,b,c). Кортежи, как и списки, могут содержать значения любых типов, но, в отличие от списков, являются неизменяемыми.

Для строк, списков и кортежей есть специальный синтаксис для обращения к части данных, называемый срезами (впервые такой синтаксис появился еще в Фортране):

>>> a = "abcde"
>>> a[3:5]
'de'
>>> a[:2]
'ab'
>>> a[4:]
'e'
>>> a[:-2]
'abc'
>>> a[::-1]
'edcba'
>>> a[5:2:-1]
'ed'
>>> a[:]
'abcde'
>>> a[::2]
'ace'

Словари в Питоне - это структура, соответствующая хэшам в Перле, массивам в PHP и std::map в C++. Записываются так:

{'a': 1, 'b': 2, 'c': 3}

или так:

dict(a=1,b=2,c=3).

В качестве индексов в словарях могут быть использованы числа, строки, и вообще, любые объекты, имеющие метод __hash__() (например, кортежи). Обращение к элементам словаря выглядит так:

print d['key']

Метод keys() возвращает список из ключей словаря, а values() - список из значений. Порядок следования ключей не определен:

>>> d = dict(a=1,b=2,c=3)
>>> d.keys()
['a', 'c', 'b']
>>> d.values()
[1, 3, 2]

Функции создаются так:

def f(x):
"Documentation string (docstring)"
return x*x

или так:

f = lambda x: x*x

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

2. Типизация.

Главное отличие Питона от Явы (если брать только чисто "лингвистические" свойства) - это типизация. Напомню, в Яве используется строгая статическая типизация с явным объявлением типов переменных. Это означает, что типы всех переменных известны в момент компиляции, и тогда же происходит проверка типов. Это дает преимущество в том плане, что значительная часть ошибок отлавливается в момент компиляции. Зато это замедляет компиляцию. В Питоне используется строгая динамическая типизация. "Динамическая" означает, что типы переменных становятся известными только во время выполнения, и тогда же выполняются проверки типов. Это дает больше возможностей написать неработающий код, но ускоряет компиляцию и дает значительную гибкость. Пример:

>> a = 20 # теперь a - это число, тип int
>> a = "a string" # а теперь - строка, тип str.

С точки зрения устройства транслятора, динамическая типизация отличается от статической тем, где хранится информация о типе переменной. В случае статической типизации информация о типе хранится вместе с именем переменной, в таблице символов транслятора (и вместе с именем переменной исчезает к моменту исполнения). В случае динамической типизации информация о типе хранится вместе со значением и доступна во время исполнения. Имя же переменной - это, в большинстве случаев, только ссылка на значение. Пример:

>>> x = [1,2,3] # x - это список
>>> y = x # y указывает туда же, куда x (копируется указатель).
>>> y[1] = 5 # изменяем элемент из y
>>> x # он изменился и в x.
[1, 5, 3]

Таким образом, в терминах C++, имя переменной - это ссылка (указатель), (void*). Исключением являются атомарные типы данных - числа и строки. Пример:

>>> a = 1
>>> b = a # здесь копируется уже не ссылка, а собственно значение
>>> b = 3 # присваиваем другое значение...
>>> a # на переменную a это не повлияло.
1

По той же схеме передаются аргументы в функцию.

За счет того, что информация о типе хранится вместе со значением, она доступна во время исполнения. Пример:

>>> a = 1
>>> type(a)
<type 'int'>
>>> a = "a string"
>>> type(a)
<type 'str'>

Таким образом, имеет смысл запись вроде if type(a) == str:...

Доклад по Python: About

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

Объем - около 3-х часов.
Что это такое. Особенности, отличия от других языков.
Плюсы и минусы. ООП в питоне. Сравнение реализации ООП в питоне и Ц++/Жаба.
Характерные приемы программирования и дизайна.

Главная задача - даже не "агитировать за питон", а просто расшевелить мозги.


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

понедельник, февраля 11, 2008

Интерпретатор lambda-исчисления

В порядке самообразования решил поподробнее изучить λ-исчисление. Нашел несколько книжек и пр. Забавной показалась теорема о существовании неподвижной точки у любой функции. Решил "проверить", переписал Y-комбинатор из книжки на знакомом мне Haskell... Угу, фиг - его невозможно типизировать. Хотел было попробовать на Scheme, но сообразил, что типизация она и в scheme типизация. Тогда стал искать какой-нибудь язык, реализующий нетипизированное λ-исчисление, чтобы на нем можно было "поиграться" с примерами из книжек. Нашел только unlambda, который, как оказалось, реализует вовсе даже не лямбду, а комбинаторную логику, а синтаксис у него похож более всего на brainfuck. Вздохнул и решил писать что-то такое сам...

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

Подробности про язык тут, само поделие тут (Depends: python, python-ply; запускать ./pylambda.py в терминале).

суббота, декабря 29, 2007

Screen-Launcher


Вот делаю такую программу. Это еще один десктоп, позволяющий запускать программы, что-то из серии idesk или rox-desktop.
Первое отличие от упомянутых проектов - кнопки запуска могут быть организованы в разделы, что упрощает ориентацию при большом количестве программ на десктопе.
Второе отличие: программа сама определяет, является ли текущий юзер 'админом' или "простым пользователем" (как - см. ниже). Добавлять/удалять/редактировать разделы и кнопки запуска могут только админы. Юзер, соответственно, может запускать только то, что ему разрешил админ.
Программа делается в основном для компьютерных клубов, но ее вполне можно использовать и дома.

Конфиг - простой текстовый файл ini-формата /etc/launcher.conf, его можно редактировать руками, а можно прямо из интерфейса самой программулины.
Админы от юзеров отличаются очень просто: считаем, что текущий юзер - админ, если у него есть право на запись в конфиг. Т.о. для отделения юзеров от админов можно использовать всю мощь традиционной unix-системы разделения прав (группы, ACL-ы и пр.).
Программа на python-gtk2.
Скрин админского интерфейса:

Посмотреть/взять можно из svn тут:
http://screenlauncher.googlecode.com/
На днях авось выложу deb-пакет.

UPD. Вставил скриншоты, выложил deb-пакет.
Предвидя вопрос: да, wallpaper-ы пока не поддерживаются.
Для меня эта задача с низким приоритетом.

UPD2. Ух ты, оно под маздаём запустилось!!! :)

суббота, января 06, 2007

И еще одна поделка на python-gtk

Сабж. Сделал удобнейшую вещь - при втыкании в usb-разъем флэшки от фотика появляется окошко "Слить фотки". Выбираем директорию, нажимаем Старт, и начинается процесс. На одном прогрессбаре показывается процесс копирования, потом на другом - процесс проявки из RAW. Т.е. практически аналог соответствующей тулзы из Гнома. Только Гном здесь совсем не нужен, не нужен ни hal, ни dbus, а нужны python, python-gtk2, python-glade-2.

Кстати, долго думал: как при подключении устройства показать окошко? Ну хорошо, любую программу при подключении флэшки можно запустить с помощью строчки где-нибудь в /etc/udev/rules.d наподобие этой:
SUBSYSTEM=="scsi", SYSFS{model}=="CF Card CF", ACTION=="add", RUN="/path/to/program"
Но ведь программа будет запущена с правами демона udevd, вне любых иксов, и вообще не знает, что иксы запущены. Ей бы надо определить, запущены ли иксы вообще, на каком именно дисплее (предусмотреть случай, когда запущены двое иксов - у меня это штатный режим), получить разрешение на подключение к этим иксам, и только тогда можно показывать окошко. Во геморрою-то...
Но, нормальные герои юниксоиды всегда идут в обход!
Обходной путь получился такой. При запуске иксов фоном запускается питоновский скрипт, который каждые 5 секунд проверяет содержимое пайпа /var/run/mphotos. Если там появляется строчка "plugged" - показывается окошко и делается вся работа по копированию и проявке фоток. Иначе ждет дальше. А udev указанным выше способом при подключении флэшки запускает примитивный скрипт, который и записывает строчку "plugged" в пайп /var/run/mphotos. Все.

четверг, января 04, 2007

Swish-e, python-gtk, glade...

Уже довольно давно я пользуюсь замечательным поисковиком swish-e. Он довольно быстро индексирует документы, а потом ищет по созданному индексу (о его близком сородиче, swish++, писал virens). Все бы хорошо, только компьютером я пользуюсь не один, вместе со мно им пользуются далекие от компьютеров родственники. И совсем мне неохота объяснять им про прелести командной строки... Так что понадобилась "графическая морда" к swish-e. Ничего полезного не нашел, пришлось писать самому. Стал подбирать, на чем писать. Прежде всего, логика предельно простая (запустить команду и перехватить стандартный вывод), поэтому желания писать на чем-нибудь типа C/C++ не возникает совсем. В идеале это должен быть bash-скрипт. Но как из bash сделать GUI? Варианты получились такие.

Xdialog. Позволяет что-нибудь спросить у пользователя, например, так:
ANSWER=$(Xdialog --stdout --inputbox "Что ищем?" 10 30)
Нет возможности сделать сколько-нибудь сложный диалог. В данном случае этот вариант отпал.

kaptain. Позволяет создавать довольно сложные диалоги, которые описываются на специальном языке. Но эти диалоги нельзя изменить после создания, т.е. они могут работать только как формы ввода. Кроме того, у kaptain большие проблемы с нелатинскими алфавитами. Опять отпадает.

gtk-server. Практически, представляет собой интерфейс к функциям gtk для любого окружения - для вызова функции нужно записать ее имя и параметры на стандартный вход gtk-server. Практически всем подходит, но shell-script с его использованием больше похож на обычную C-программу с использованием gtk. Пока отложил в сторонку.

А остановился я в итоге на связке python + python-gtk + glade. Python - скриптовый, достаточно высокоуровневый, язык, так что простая логика на нем занимает мало места. Сам интерфейс нарисовал с помощью Glade (визуальный конструктор интерфейсов для gtk), сохранил в .glade-файле (это xml), а из python-скрипта он только подгружается (с помощью python-glade). Скрипт получился довольно скромного размера, соответственно своей простой функциональности. Кому интересно, взять можно здесь: http://portnov84.narod.ru/downloads/sw-search-0.1b.tar.gz (Depends: swish-e, python, python-gtk2, python-glade-2).