Посты с тегом python (37)

Бойтесь lxml, html парсящий

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

u'\xd0\x9a\xd1\x83\xd1\x80\xd1\x83\xd0\xbc\xd0\xbe\xd1\x87'

Вы заметили что что-то не так? И я вот. Строчки как бы уникодные, но внутри них закодированные utf-8 байты. Что-то pдесь не так. Разбираясь дальше и потребовав скрипт, которые такое генерирует, становится понятно, что данные берутся из веба. Вполне обычным способом через urllib и потом скармливаются в lxml.html для разбора. Поскольку urllib оперирует только байтовыми строками, то он не мог их так превратить в уникод, а значит во всем виноват lxml.

Вообще lxml очень крутая библиотека - и быстрая, и функциональная, и умеет мимикрировать интерфейсом под ElementTree, и взаимодействовать с BeatifulSoup. Она давно уже пользуется популярностью у питонистов, когда надо как-то удобно работать с xml.

Но тут немного другой случай. Тут используется парсер html. И именно в нем происходят эти неприятные метаморфозы со строками.

Я решил понять в чем же всё-таки и дело и как побороть такое поведение.

Для начала, я сходил на http://yandex.ru/ и посмотрел что за html там отдается. Кодировка контента utf8. Сразу что бросилось в глаза -- это ...

Гринлеты

Примерно год назад, во время массовой истерии по поводу питонячих и не очень асинхронных серверов, я, пытаясь составить своё мнение обо всем об этом, набрел на интересную библиотеку - greenlet. На которой базируется сетевая библиотека eventlet, а на ней всю очередь WSGI веб-сервер spawning.

Как видно из названия, она позволяет делать легкие треды - гринлеты в обычном не stackless питоне. Я написал небольшой скрипт "на попробовать":

#!/usr/bin/env python
from greenlet import greenlet

def dispatch():
   while True:
       for w in workers:
           w.switch()
           print 'dispatch'

       if not all(workers):
           break

d = greenlet(dispatch)

def worker(name):
   for i in range(5):
      print name, i
      if i % 2 == 0:
         print 'switch'
         d.switch()

workers = [
   greenlet(lambda: worker('foo')),
   greenlet(lambda: worker('bar'))
]

print 'start'

d.switch()

print 'done'

вывод:

start
foo 0
switch
dispatch
bar 0
switch
dispatch
foo 1
foo 2
switch
dispatch
bar 1
bar 2
switch
dispatch
foo 3
foo 4
switch
dispatch 
bar 3
bar 4
switch
dispatch
done

и вот недавно набрел на него. Правда прекрасно? Такой простор для творчества - передача контекста выполнения в произвольное место с сохранением состояния. Некий намек на потусторонний call/cc из "других" языков.

Ещё тогда у меня родилась идея где это можно ...

Что такое pip?

Pip это альтернатива easy_install, а как говорят сами разработчики - замена.

Как известно easy_install только часть глыбы под названием setuptools. Много копий сломано по поводу нужно ли такие двухголовое чудовище, которое позволяет продвинутым образом собирать питонячьи пакеты и их устанавливать. Или достаточно стандартного distutils. Вот тут pip выступает как противоположность, говоря - я сборкой пакетов не занимаюсь, а только их ставлю.

Pip может поставить любой пакет собранный при помощи distutils. Причем только source-пакет - никаких бинарных яиц ему и прочих setuptools'овых прибамбасов.

Эта концептуальная простота во многом помогла pip постепенно выйти на уровень широко используемого инструмента в питон-сообществе. Благо и пользоваться им максимально просто:

# pip install wna

где wna - это некий пакет (для примера я возьму код своего блога).

Так же преимуществом pip безусловно является более полезный вывод информации о процессе установки и репортинг ошибок во всяких непредвиденных ситуациях.

Помимо уже собранных source-пакетов pip может брать исходники пакетов из систем контроля версий. Поддерживаются subversion, mercurial, git, bazaar. Делая checkout и устанавливая через python setup.py с ключом devel, дает возможность иметь в папке /src/packet_name/ исходный код и при необходимости редактировать его.

# pip install -e hg+http://bitbucket.org/daevaorn/turbion/#egg=turbion

Где egg=turbion говорит pip чтобы он сделал checkout ...

virtualenv: виртуальные окружения

Много у вас разных питонячих проектов работают на одном сервере? У меня вот да. Все ли они использую одни и те же библиотеки или версии библиотек? Нет. Так как сделать, чтобы можно было удобно поддерживать всё это многообразие?

Давным дано я предлагал один вариант - использовать svn:externals и таскать зависимости (код сторонних библиотек и приложений) с собой. Но такой способ очень ограничен в своем применении. Вы должны использовать VCS (subversion или иные поддерживающие аналогичные концепции) для развертывания проекта на сервере и все зависимости тоже должны быть доступны в той же системе контроля версий. Но так случается очень редко. Да и потом далеко не всегда VCS вообще используются для выкладки проектов. Пакетные системы во многих случаях удобней.

Создание изолированных окружений задача довольно давнишняя. И в питонячем мире решается разными способами уже давно. Одним из инструментов является - virtualenv.

Создать окружение просто:

virtualenv myenv

После выполнения этой команды создается директория myenv в который находится некое подмножество unix-like корневой файловой системы. В директории myenv/bin будет лежать бинарник питона, и несколько дополнительных скриптов. В myenv/lib - дерево каталогов, повторяющее оное у текущего установленного питона в системе.

Для того чтобы питонячий код работал в этом окружении, его надо запускать, используя myenv/bin/python, или подключив ...

Принуждение к порядку

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

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

Классический способ

Для заданного класса, который будет классом атрибутов, нуждающихся в упорядочивании (для джанги это models.Field и forms.Field), заводится "статический" счетчик, который, обновляясь при каждом создании объекта данного класса, хранит порядковый номер этого самого объекта. В дальнейшем по этому номеру поля сортируются и заносятся в какой-либо упорядоченный контейнер - например в SortedDict.

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