python

Скороговорка

Пайтонячья скороговорка:

import pprint as pprint
pprint.pprint(pprint)

Добавлено 24 декабря 2009 14:08: вариант от Бобука, чуть-чуть поправленный мной:

from pprint import pprint as pprint
locals()[pprint.__name__](pprint)

4 комментария

Сколько памяти съела программа на Python?

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

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

Следующий код (используется модуль Heapy):

from guppy import hpy
h = hpy()
print h.heap()

выведет вот такую, достаточно информативную, табличку:

Partition of a set of 132527 objects. Total size = 8301532 bytes.
Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
0  35144  27  2140412  26   2140412  26 str
1  38397  29  1309020  16   3449432  42 tuple
2    530   0   739856   9   4189288  50 dict (no owner)

Другое решение — использовать Dowser, его интересная особенность — можно наблюдать за работающим кодом через веб-браузер.

# memdebug.py

import cherrypy
import dowser

def start(port):
    cherrypy.tree.mount(dowser.Root())
    cherrypy.config.update({
        'environment': 'embedded',
        'server.socket_port': port
    })
    cherrypy.server.quickstart()
    cherrypy.engine.start(blocking=False)

Подключаем в программу memdebug и вызываем memdebug.start.

Там же упоминается модуль PySizer, но его автор забросил работу и рекомендует использовать Heapy.

Оставить комментарий

Пайтон такой Пайтон

В regexp никаких тебе «(?R)», нет once-only subpatterns, нет юникодных диапазонов, нет «\G». Сижу, как дурак, клепаю конечные автоматы. Сделать рекурсию в регулярках с одними группами, но без «once-only subpatterns» я ниасилил, а без «\G» автоматы плохо стыкуются с регулярками. Не могли PCRE что ли взять? Зачем одноколёсный велосипед было писать?

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

Вдох-выход.

16 комментариев

Комментарии в Python

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

Во-вторых, попробуйте в большом списке (list) закомментривать большой кусок. Тройные кавычки тут не подойдут:

mylist = [\
'раз',
'два',
"""'три',
'четыре',"""
'пять'
]

Всем очевидно почему не подойдут? Я не понимаю почему до сих пор в языке нет многострочных комментариев.

20 комментариев

Утечка памяти в urllib2

Наткнулся сегодня на печальное поведение библиотеки urllib2 — в версии, которая используется в Ubuntu Hardy (Python 2.5.2) утекает память. Не знаю точно в какой версии баг исправили, но в, возможно, 2.5.4 и 2.6.2 бага уже нет.

За ночь с демона, использующего urllib2, «натекло» 9Гб.

Добавлено 14 августа 2009 15:15: urllib с httplib тоже утекают.

4 комментария

Такой молодой Python

Девятнадцать лет языку, а модуля для него, который поддерживал бы сочетание SOAP+NTLM+HTTPS+WSDL-от-Excahnge 2007 так и нет. Ни одна из SOAP библиотек для Python не умеет это всё сразу. И большинство из них даже не смогли разобрать (полностью валидный) WSDL SOAP Exchange 2007!

24 комментария

Как сменить имя процесса в Python

Написал тут небольшой демон на Пайтоне и обнаружил, что «тушить» его через start-stop-daemon неудобно.

Start-stop-daemon — утилита для запуска и остановки демонов, обычно для останова ей передаётся параметр «stop» и ещё два параметр: имя файла, где записан PID процесса, который надо остановить и имя этого процесса. Утилита, прочитав PID, должна проконтролировать — действительно ли на данном PID работает процесс с заданным именем.

Засада в том, что демон на Пайтоне и имя процесса у меня — «python». Я в этом языке программирования пока новичок (программирую с сентября), возможно поэтому не нашёл в документации как это делается (Ваня, расскажи, если есть простой способ).

Я помню, что в Perl было очень просто — записал в переменную $0 имя и в списке процессов видно записанное значение. В Python запись в sys.arvg[0] ничего не даёт.

В общем, мой велосипед (только для Linux!):

import os.path, sys, ctypes

ctypes.CDLL('libc.so.6').prctl(15, os.path.basename(sys.argv[0]), 0, 0, 0)

Не все утилиты «увидят» новое имя, но для start-stop-daemon и killall этот способ работает.

2 комментария

Feedparser и санитайзер в Python

Странно, но в модуле feedparser для Python не нашёл способа как выключить внутренний санитайзер. Зачем его выключать? Во-первых, он плохенький (например, просто убивает все inline-стили), во-вторых, понадобилось брать RSS из доверенного источника, где санитайзить нечего.

Сделал monkey patch:

import feedparser
feedparser._sanitizeHTML = lambda html, encoding: html

5 комментариев

Ремейк старой шутки

# -*- coding: utf-8 -*-

True, False = False, True # happy debugging!

Это Python, для тех, кто не понял.

5 комментариев

Определить начало дня в Python

Искал в Python способ переставить время на начало дня. В Python для манипулирования датами есть довольно удобный (если не считать временных зон) модуль datetime, получилось два варианта: первый придумал сам, второй подсказал (ещё раз спасибо) Иван Сагалаев:

import datetime

currentdate = datetime.datetime.today()
print currentdate.replace(hour=0, minute=0, second=0, microsecond=0)
import datetime

currentdate = datetime.datetime.today()
print currentdate.combine(currentdate.date(), currentdate.min.time())

Оставить комментарий

Python 2.4 не освобождает память

Как я уже не раз говорил, libmapi нещадно течёт. Я сейчас работаю с версией из транка, 921-й комит, это довольно старая версия (текущий коммит — 1303), но утечки находят до сих пор и впереди их немало. Память течёт быстро и при получении больших объёмов данных утекает раньше, чем данные удаётся получить.

Поэтому я изолирую libmapi в отдельном процессе, получаю данные, пока могу и отдаю их «наверх» — родителю, потом делаю новый процесс и получаю следующую пачку. Для того, чтобы отдать данные родителю я использую os.pipe, а данные туда можно передавать только строкой. Так как данные у меня сложные, я их сериализую при помощи cPickle, а для сериализации нужна память.

Где взять память, если всё, что было доступно процессу, съела libmapi? Я пытался выделать память при помощи list(range(20 * 1024 * 1024)), а перед сериализацией удалять, но это не работало. Сегодня я узнал почему. Оказывается Python до версии 2.5 никогда не освобождает память. У меня как раз Python 2.4, приехали.

P.S. Чтобы предотвратить вопросы для чего я делаю list(range(…)), расскажу сразу: для совместимости с Python 3.0, на который мы все когда-нибудь перейдём. Там range возвращает итератор.

Добавлено позднее.

Я сейчас немного поисследовал, сделал замеры и выяснил, что если обращаться к аллокатору памяти Python напрямую (например, через ctypes), то память возвращается:

from ctypes import *

mem = pythonapi.PyMem_Malloc(c_size_t(100*1024*1024))

pythonapi.PyMem_Free(mem)

Оставить комментарий

Век живи, век учись, от нехватки памяти умрёшь

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

Есть модуль, внутри него происходит fork — внутри потомка изолируется libmapi, у которой нещадно течёт память. Модуль используется внутри программы, которая перехватывает все exceptions (!). Знаю, что это неправильно, но она это делает в режиме отладки, для отладки и используется.

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

sys.exit пораждает иключение и выходит с его помощью. Если где-то это исключение блокируется, то потомок, натурально, не может умереть. Решение простое — выходить надо по os._exit.

4 комментария

Python: while 1

Starting with Py2.3, the interpreter optimizes «while 1» to just a single jump. In contrast «while True» takes several more steps. While the latter is preferred for clarity, time-critical code should use the first form.

Вот как…

4 комментария

Ctypes и alloc

В биндинге для Python libmapi (который я пишу, с использованием ctypes), возникла следующая задача: есть куски кода, где надо выделить память через вызовы talloc. Желательно, чтобы память сама освобождалась, когда переменная, в которой у меня указатель на эту структуру, исчезает.

Оказывается, в ctypes можно легко обернуть эти вызовы в свой класс и использовать обычный деструктор, чтобы это сделать. При этом, ctypes, чтобы получить внутреннее значение, просто берёт свойство «_as_parameter_» класса.

Получился вот такой код:

class mapi_talloc(object):
    def __init__(self, lib):
        self._lib = lib
        self._as_parameter_ = lib.talloc_init('python-mapi-object:' + str(hash(self)))

    def __del__(self):
        self._lib.talloc_free(self._as_parameter_)

Оставить комментарий

Магия libmapi

Можно я опять про libmapi?

В общем, из libmapi мне приходят два магических числа, они содержат диапазон — начало и конец периода, данные за который возвращает функция GetUserFreeBusyData. По внешнему виду видно, что это не unix timestamp (для несведующих, Unix timestamp — это число секунд с 1 января 1970 года).

По коду libmapi пытался выяснить что это. Код разбросан по файлам, но, в итоге, выглядит так:

// в t приходит <магическое число>

uint32_t t;
uint64_t time;
struct timespec ret;

time = t
time *= 60;
time *= 10000000;

ret.tv_nsec = (long) ((time % (1000 * 1000 * 10)) * 100);
d /= 1000*1000*10;

d -= 11644473600L;

// тут ещё парочка условий, которые не добавляют смысла

ret.tv_sec = (time_t)d;

В таком виде я так и не смог понять что это за число. Порыскав по поисковикам, наткнулся на пост, где упоминается, что это всего лишь число минут с 1 января 1601 года.

Так что мой итоговый код на Python выглядит (без системных вызовов) так:

publish_start, publish_end = map(
    lambda x: x * 60 - 11644473600, # 369 лет
     (publish_start, publish_end)
)

Оставить комментарий

Libmapi писали индусы?

Есть ощущение, что некоторые части libmapi писали индусы. Напоминаю, что libmapi — библиотека для общения с Microsoft Exchange Server, к которой я пишу биндинг в Python с использованием ctypes.

Некоторые вызовы я предпочёл переписать на Python, так как код содержит баги и глупости. Например, мне приходит (в терминах Python) массив событий. Приходят два «слова», два числа, которые содержат дату/время старта и окончания. Числа приходят в минутах, например, чтобы узнать какой там указан день, нужно нацело разделить число на 1440 (количество минут в сутках) и прибавить один.

В итоге, я вычисляю всё вот так (часовой пояс я ещё не умею определять):

last   = event_end - event_start
        day    = event_start // 1440
        hour   = (event_start - day * 1440) // 60
        minute = event_start % 60
        day += 1

Авторы libmapi делают куда затейлевее:

for (hour = 0; hour < 24; hour++) {
    if (!((event_start - (60 * hour)) % 1440)) {
        // значит мы нашли час, который указан в событии

        day = ((event_start - (60 * hour)) / 1440) + 1;
        last = event_end - event_start;
    }
}

7 комментариев

Эпопея с MAPI

Все эти пляски с ctypes — следствие одной большой задачи, с которой мы столкнулись в группе внутренних сервисов. Интеграция нашего интранета с Microsoft Exchange Server — это сервер для обмена сообщениями и совместной работы. Основной протокол, по которому этот сервер работает — MAPI.

После небольшого исследования выяснилось, что направлений для дальнейшего исследования три: можно попытаться работать с MAPI через RPC, можно использовать библиотеки, которые только-только начали бурно расти (т. к. Microsoft открыла протокол) или использовать Windows-машину, на которой сделать RPC-обёртку для готового клиента Exchange, COM-объекта.

Последний вариант, с COM-объектом, нравится нам всем меньше всего и является запасным, в случае, если ничего больше не заработает.

Я долго не мог выяснить что за RPC применяет Microsoft, пока не начал перебирать всё, что приходит на ум. Оказалось, что Microsoft, в основном, использует диалект DCE-RPC, который называется MSRPC. C DCE-RPC, да и вообще с технологиями Microsoft (NMB, SMB) из Python можно работать модулем impacket. Библиотека не обновлялась два года, но работает.

Документации на него — кот наплакал, чуть примеров и в Сети есть несколько эксплоитов, написанных при помощи этого модуля. Этого хватило, чтобы понять как примерно им пользоваться и несколько дней и я пытался сделать прототип для общения с Exchange на нём.

Довольно быстро оказалось, что задача очень трудоёмкая. Даже для того, чтоб описать только вызовы верхнего уровня, ушёл бы не один день, а описывать MAPI-вызовы внутри DoRCP… В общем, от этой идеи пришлось отказаться.

Готовых библиотек для работы с MAPI немного. Бесплатная, кажется, так и вовсе одна — libmapi. Сейчас стабильной версией считается 0.7, поскольку библиотека требует присутствия Samba4, то решено было ставить её из исходников. Оказалось, дело это очень непростое, так как база пакетов на машине, куда пришлось её ставить была разрушена, «Пайтонов» было два, от чего у исталлятора уносило крышу и имелась собственная Samba3, которая используется в работе.

Наши админы попробовали её поставить, но после нескольких проблем, сказали, что это очень сыро и ставить не получается. Я их понимаю — работы у них масса, слишком много времени на исталляцию недописанного софта они выделять не в состоянии. Поэтому библиотеку я ставил сам. В рунете я не смог найти никаких описаний того, что это кто-то делал, поэтому несколько советов.

Для начала, лучше всего ставить версию из транка. Встаёт она значительно проще, чем стабильная. Далее. Сначала надо поставить Samba4, для этого в каталоге «script» есть специальный скрипт, который выкачивает её и ставит. Посколько API Samba меняется очень часто (это «альфа»), выкачивать нужно определённую ревизию, скрипт это умеет, но ему нужен git. Git — это распределённая система контроля версий, это пакет в Debian называется git-core, а не git. Git нужен последней версии (sic!), иначе скрипт скачать ревизию не сможет.

Как оказалось, Samba4 в памяти не нужна. Всё что он неё надо, чтобы она была скомпилена со всеми библиотеками и небольшой конфиг, образец которого есть в дебрях libmapi. Не забудьте правильно выставить язык в конфиге, в небольшой инструкции по установке есть описание того, как это делается. В установочных файлах конфига проследите, чтобы все пути были указаны правильно. Например, мне пришлось поправить пути до исходников Samba с «samba4» на «samba4/source4», а так же руками добавлять PKG_CONFIG_DIR в файлы configure.

Дайте libmapi и Samba4 весь софт, который она попросит. Даже опциональный. И самый свежий (к Python это не относится, у меня взлетело с 2.4). Это важно. Некоторые файлы нужно будет перенести вручную, например, библиотеки для Python. После компиляции создайте профиль (в wiki libmapi есть инструкция) и запустите openchangeclient, чтобы проверить соединение. Если у вас домен, то не забудьте про утилиту provision (описание можно найти в инете, важно пускать её именно так как в примерах — чтобы текущей была указанная в примерах папка).

Если openchangeclient выдаёт какую-то ерунду, воспользуйтесь strace, libmapi укладывает не все файлы как надо, посмотрите что openchangeclient пытается открыть и не находит. Некоторые файлы я перекладывал руками, в дистрибутиве всё есть, просто не всё попадает в свои папки.

Кажется, это всё. Да, если у вас несколько «Пайтонов» на машине, удалите все, кроме одного, или перепишите место, где в конфигах определяется версия «Пайтона» руками (я так и сделал), иначе часть модулей, возможно, встанет не туда, куда ожидается. Python нужен для работы provision (кстати, некоторые утилиты написаны на Perl, но с ним проблем у меня не было).

После всех боданий, из Си удалось подключиться к Exchange и получить данные. Из Python пока удаётся только залогиниться, но это дело времени — нужно описать нужные структуры и понять что куда передавать. В документации, кстати, есть неточности, лучше читать исходники.

6 комментариев

Python Ctypes: автоматическая генерация

Столкнулся с проблемой: нужно дёргать из Python библиотеку, очень богатую на структуры. Я эксперементирую с ctypes, которая ничего не знает об *.h-файлах, поэтому структуры приходится описывать руками. На 20-й структуре я сломался.

Оказывается, есть утилита, которая генерирует структуры по заголовочным файлам автоматически. Называется h2xml.py, в интернете есть небольшое описание. Попробую её.

Единственный недосток: язык — Cи++, с Си почему-то утилита не работает.

6 комментариев

Ctypes

У PHP есть модуль из которого можно «дёрнуть» любую dll/so. Следовало ожидать, что и у Python есть что-то подобное. Это что-то подобное называется ctypes. Я попробовал реализовать небольшой модуль, который обращается к библиотеке libmagic (которая занимается определением типа файла по его сигнатуре).

Вот что получилось (finfo.py):

# -*- coding: utf8 -*-

from ctypes import *

NONE = 0 #  No special handling
DEBUG = 1 # Print debugging messages to stderr
SYMLINK = 2 # If the file queried is a symlink, follow it
COMPRESS = 4 #  If the file is compressed, unpack it and look at the contents
DEVICES  = 8 #  If the file is a block or character special device, then open the device and try to look in its content
MIME = 16 # Return a mime string, instead of a textual description
CONTINUE = 32 #  Return all matches, not just the first
CHECK = 64 # Check the magic database for consistency and print warnings to stderr
PRESERVE_ATIME = 128 # On systems that support utime(2) or utimes(2), attempt to preserve the access time of files analyzed
RAW = 256 #  Don’t translate unprintable characters to a ooo octal representation.

class finfo:
    def __init__(self, magicfile = None, flag = NONE):
        """Инициализация. Передаются имя magic-файла (или используется размещение
        по умолчанию и флаги (см. константы выше)"""

        self.lib = cdll.LoadLibrary('libmagic.so.1')

        # следующие функции возвращают указатель на char, что я указываю
        self.lib.magic_buffer.restype = c_char_p
        self.lib.magic_file.restype = c_char_p
        self.lib.error.restype = c_char_p

        self.magic = self.lib.magic_open(flag)
        self.lib.magic_load(self.magic, magicfile)

    def buffer(self, content):
        """определить содержимое из строки"""
        return self.lib.magic_buffer(self.magic, content, len(content))

    def file(self, filename):
        """определить содержимое файла"""
        return self.lib.magic_file(self.magic, filename)

    def __del__(self):
        self.lib.magic_close(self.magic)

Вызывается модуль просто:

import finfo

mime = finfo.finfo('/usr/share/file/magic', finfo.MIME)
print mime.file('more.png')

10 комментариев

← 1 2 3 4 →