Архив рубрики: Python

Динамические атрибуты в python. Часть 1, теоретическая — "в поисках атрибута"

Disclamer: рассмотрены только новые классы — все наследуемые от object. В «старых» классах все работает немного по-другому.

Во всей статье классы именуются с заглавной буквы, а экземпляры классов со строчной. A это класс экземпляра a.

Вопрос о том что происходит когда python исполняет конструкцию a.b очень важен для понимания многих других тем. Особенно учитывая что имитация атрибутов один из наиболее часто используемых приемов для написания библиотек в pythonic стиле.

Сначала посмотрим где принципиально может быть '»b»'. Из документации python можно узнать о следующих возможных вариантах.

  • A.__getattrib__(a, «b»)
  • 'a.__dict__[«b»]'' ; пока без '__slots__
  • 'A.__dict__[«b»]'', вместо 'A может быть один из базовых для A классов.
  • A.__dict__[«b»](a) — свойство (property)
  • A.__getattr__(a, «b»)

A.__getattrib__(a, «b») гарантировано вызывается первым, A.__getattr__(a, «b») последним, а a.__dict__[«b»] имеет приоритет над 'A.__dict__[«b»]'. Без ответов остаются вопросы о приоритете property и о том, что происходит если на отдельных фазах будут возбужденны исключения.

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

Итак скачиваем последнюю версию исходников cpython и будем погружаться. Все начинается в cpython/Python/ceval.c со строки вида TARGET(LOAD_ATTR) (у меня строка 2228). Мы находимся прямо в сердце виртуальной машины сpython, в цикле eval — здесь сpython по очереди исполняет инструкции байтокода. dis.dis говорит нам, что a.b компилируется в две инструкции:

    >>> import dis
>>> dis.dis(lambda : a.b )

1 0 LOAD_GLOBAL 0 (a)
3 LOAD_ATTR 1 (b)
6 RETURN_VALUE

Так что инструкция LOAD_ATTR это как раз то, что нам нужно. Тело TARGET(LOAD_ATTR) содержит стандартную возню со стеком, подсчет ссылок и интересующий нас вызов:

Hightlited/Raw

x = PyObject_GetAttr(v, w); //Здесь 'v' - 'a', а 'w' -  'b'.

x = PyObject_GetAttr(v, w); //Здесь 'v' - 'a', а 'w' -  'b'.

Hightlited/Raw

//Ф-ция PyObject_GetAttr находится в cpython/objects/object.c

PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
// получаем тип v
PyTypeObject *tp = Py_TYPE(v);

// бла-бла-бла, проверка типов
// получение атрибута
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);

// не важный код
}

//Ф-ция PyObject_GetAttr находится в cpython/objects/object.c

PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
// получаем тип v
PyTypeObject *tp = Py_TYPE(v);

// бла-бла-бла, проверка типов
// получение атрибута
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);

// не важный код
}

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

Hightlited/Raw

//cpython/Objects/typeobject.c:3260

PyTypeObject PyBaseObject_Type < span style="color: #666666">= { // эта девушка! (с)
// bla-bla-bla
"object", /* tp_name */
// bla-bla-bla

// __getattribute__
PyObject_GenericGetAttr, /* tp_getattro */
// .......
}


// PyObject_GenericGetAttr:
// cpython/objects/object.c

PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
}

// cpython/objects/object.c
// первый солидный кусок кода
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{
PyTypeObject *tp = Py_TYPE(obj);

.......

if (!PyUnicode_Check(name)){
.... // исключение
}
else
Py_INCREF(name);

// ......

// ищем name по mro
descr = _PyType_Lookup(tp, name);

.....

if (descr != NULL) {

// если у descr есть __get__ - перед нами дескриптор
f = descr->ob_type->tp_descr_get;

// если дескриптор данных
if (f != NULL && PyDescr_IsData(descr)) {

// получаем из него значение и выходим
res = f(descr, obj, (PyObject *)obj->ob_type);
Py_DECREF(descr);
goto done;
}
}

// dict это параметр для рекурсивного поиска
// при вызове из tp_getattro он всегда NULL
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
// тут получаем смещение __dict__ внутри объекта
// ну и сам __dict__ по нему
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}

// если dict нашелся
if (dict != NULL) {
Py_INCREF(dict);

// получаем a.__dict__['b']
// если 'b' not in a.__dict__, то PyDict_GetItem установит
// исключение и вернет NULL
// PyObject_GetAttr затем заменит это исключение(KeyError) на AttributeError
res = PyDict_GetItem(dict, name);
if (res != NULL) {
....
goto done;
}
....
}

// возвращаемся к дескриптору
// если он хоть какой-нить
// используем его
if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
Py_DECREF(descr);
goto done;
}

// если вообще нашли что-то в базовых классах
if (descr != NULL) {
res = descr;
/* descr was already increfed above */
goto done;
}

// иначе raise AttributeError
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
done:
Py_DECREF(name);
return res;
}

//cpython/Objects/typeobject.c:3260

PyTypeObject PyBaseObject_Type = { // эта девушка! (с)
// bla-bla-bla
"object", /* tp_name */
// bla-bla-bla

// __getattribute__
PyObject_GenericGetAttr, /* tp_getattro */
// .......
}


// PyObject_GenericGetAttr:
// cpython/objects/object.c

PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
}

// cpython/objects/object.c
// первый солидный кусок кода
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{
PyTypeObject *tp = Py_TYPE(obj);

.......

if (!PyUnicode_Check(name)){
.... // исключение
}
else
Py_INCREF(name);

// ......

// ищем name по mro
descr = _PyType_Lookup(tp, name);

.....

if (descr != NULL) {

// если у descr есть __get__ - перед нами дескриптор
f = descr->ob_type->tp_descr_get;

// если дескриптор данных
if (f != NULL && PyDescr_IsData(descr)) {

// получаем из него значение и выходим
res = f(descr, obj, (PyObject *)obj->ob_type);
Py_DECREF(descr);
goto done;
}
}

// dict это параметр для рекурсивного поиска
// при вызове из tp_getattro он всегда NULL
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
// тут получаем смещение __dict__ внутри объекта
// ну и сам __dict__ по нему
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}

// если dict нашелся
if (dict != NULL) {
Py_INCREF(dict);

// получаем a.__dict__['b']
// если 'b' not in a.__dict__, то PyDict_GetItem установит
// исключение и вернет NULL
// PyObject_GetAttr затем заменит это исключение(KeyError) на AttributeError
res = PyDict_GetItem(dict, name);
if (res != NULL) {
....
goto done;
}
....
}

// возвращаемся к дескриптору
// если он хоть какой-нить
// используем его
if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
Py_DECREF(descr);
goto done;
}

// если вообще нашли что-то в базовых классах
if (descr != NULL) {
res = descr;
/* descr was already increfed above */
goto done;
}

// иначе raise AttributeError
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
done:
Py_DECREF(name);
return res;
}

Выясним что такое PyDescr_IsData:

Hightlited/Raw

//cpython/Include/descrobject.h
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)

//cpython/Include/descrobject.h
#define PyDescr_IsData(d) (Py_TYPE(d)->tp_descr_set != NULL)

Переведем на почти python

Hightlited/Raw

def PyObject_GenericGetAttr(obj, name):
tp_attr = getattr(obj.__class__, name, NULL)
f = getattr(tp_attr, '__get__', NULL)
if f != NULL:
if hasattr(tp_attr, '__set__'):
return f(obj)
if obj.have_a_slot(__dict__):
return obj.__dict__[name]
if f != NULL:
return f(obj)
if tp_attr != NULL:
return tp_attr
raise AttributeError('......')

def PyObject_GenericGetAttr(obj, name):
tp_attr = getattr(obj.__class__, name, NULL)
f = getattr(tp_attr, '__get__', NULL)
if f != NULL:
if hasattr(tp_attr, '__set__'):
return f(obj)
if obj.have_a_slot(__dict__):
return obj.__dict__[name]
if f != NULL:
return f(obj)
if tp_attr != NULL:
return tp_attr
raise AttributeError('......')

Ситуация проясняется, переходим к type.__new__.

Hightlited/Raw

//cpython/objects/typeobject.c 
static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// ... 453 строки разного, не важного для нас сейчас кода

/* Put the proper slots in place */
fixup_slot_dispatchers(type);

return (PyObject *)type;
}

//cpython/objects/typeobject.c 
static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// ... 453 строки разного, не важного для нас сейчас кода

/* Put the proper slots in place */
fixup_slot_dispatchers(type);

return (PyObject *)type;
}

fixup_slot_dispatchers должен настроить слоты создаваемого объекта (в python CAPI слотами называются все поля PyObject tp_getattr/tp_getattro — слоты). fixup_slot_dispatchers использует slotdefs для обновления слотов. Это стуруктура содержит разнообразную информацию о слотах по умолчанию для классов. Cледующая остановка — slot_tp_getattro и slot_tp_getattr_hook — именно они описанны, как значения по умолчанию для tp_getattro в slotdefs.

Hightlited/Raw

// Используется если __getattribute__ перегружен а __getattr__ - нет.
// Изначально fixup_slot_dispatchers всегда помещает в объект
// slot_tp_getattr_hook и уже она произведет подмену себя на
// slot_tp_getattro, если в объекте нет __getattr__
// __getattribute__ есть всегда

static PyObject *
slot_tp_getattro(PyObject *self, PyObject *name)
{
static PyObject *getattribute_str = NULL;
return call_method(self, "__getattribute__", &getattribute_str,
"(O)", name);
}


static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(self);

.....
// всякие проверки
.....

// если нет __getattr__ вызываем slot_tp_getattro
// и подменяем слот на slot_tp_getattro для будущих вызовов
getattr = _PyType_Lookup(tp, getattr_str);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}


getattribute = _PyType_Lookup(tp, getattribute_str);

// если __getattribute__ не определен, или если он
// ссылается на PyObject_GenericGetAttr
// маленькая оптимизация
if (getattribute == NULL ||
(Py_TYPE(getattribute) == &PyWrapperDescr_Type &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
// ищем атрибут, используя PyObject_GenericGetAttr
res = PyObject_GenericGetAttr(self, name);
else {
// есть __getattribute__, оличный от PyObject_GenericGetAttr

Py_INCREF(getattribute);
res = call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
}

// если ничего не нашли и есть исключение PyExc_AttributeError - очищаем исключение
// и вызываем self.__getattr__

if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
res = call_attribute(self, getattr, name);
}
Py_DECREF(getattr);
return res;
}

// Используется если __getattribute__ перегружен а __getattr__ - нет.
// Изначально fixup_slot_dispatchers всегда помещает в объект
// slot_tp_getattr_hook и уже она произведет подмену себя н а
// slot_tp_getattro, если в объекте нет __getattr__
// __getattribute__ есть всегда

static PyObject *
slot_tp_getattro(PyObject *self, PyObject *name)
{
static PyObject *getattribute_str = NULL;
return call_method(self, "__getattribute__", &getattribute_str,
"(O)", name);
}


static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(self);

.....
// всякие проверки
.....

// если нет __getattr__ вызываем slot_tp_getattro
// и подменяем слот на slot_tp_getattro для будущих вызовов
getattr = _PyType_Lookup(tp, getattr_str);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}


getattribute = _PyType_Lookup(tp, getattribute_str);

// если __getattribute__ не определен, или если он
// ссылается на PyObject_GenericGetAttr
// маленькая оптимизация
if (getattribute == NULL ||
(Py_TYPE(getattribute) == &PyWrapperDescr_Type &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
// ищем атрибут, используя PyObject_GenericGetAttr
res = PyObject_GenericGetAttr(self, name);
else {
// есть __getattribute__, оличный от PyObject_GenericGetAttr

Py_INCREF(getattribute);
res = call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
}

// если ничего не нашли и есть исключение PyExc_AttributeError - очищаем исключение
// и вызываем self.__getattr__

if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
res = call_attribute(self, getattr, name);
}
Py_DECREF(getattr);
return res;
}

Теперь можно восстановить всю картину: a.b => Если:

  • __getattribute__ был перегружен, то вызывается A.__getattribute__(a, «b»).
  • есть свойство-данное — вызывается оно A.b.__get__(a)
  • b присутствует в a.__dict__, возвращаем a.__dict__[«b»]
  • есть какое-нить свойство — вызывается оно A.b.__get__(a)
  • b найдено в A.__dict__ или в __dict__ одного из базовых для A классов, => X.__dict__[«b»]
  • Если не перегружен __getattr__ — исключение/результат возвращаются
  • Если перегружен __getattr__ и ничего не найдено и тип исключения AttributeError — вызывается A.__getattr__(a, «b»)
  • исключение/результат возвращаются

Очень Важная Картинка

Нетривиальный результат:

Hightlited/Raw

class X(object):
def __getattribute__(self, name):
raise AttributeError(name)

f = 1

def __getattr__(self, name):
return name

class X(object):
def __getattribute__(self, name):
raise AttributeError(name)

f = 1

def __getattr__(self, name):
return name

В таком классе поле f будет всегда игнорироваться, но если __getattribute__ убрать — то все вернется на свои места и 'X().f' будет равно 1, а 'X().d == d'.

После просмотра кода возникает вопрос — какой ценой достается эта гибкость? Замеряем скорость доступа к разным вариантам атрибутов:

Hightlited/Raw

# глобальная переменная
global_var = 1

# пустая функция
def empty_func():
pass

# доступ к глобальной переменной из функции
def global_var_from_func():
global_var

# доступ к локальной переменной из функции
def local_var_from_func():
local_var = 1
local_var

# сложение целых
a = 1
# int + int
a + a

class A1(object):
def __getattribute__(self, name):
return None

class A2(object):

def get_b(self):
return None

def set_b(self, val):
pass

b = property(get_b, set_b)

class A3(object):
def __init__(self):
self.b = None

class A4(object):
def get_b(self):
return None

b = property(get_b)

class A5(object):
b = None


# создаем иерархию со 128ю уровнями вложенности наследования
# атрибут 'b' будет у "самого базового" класса
# в использовать для доступа будет экзампляр "самого дочернего"

class A6(object):
b = None

for i in range(127):
A6 = type('A6', (A6,), {})

a6 = A6()

class A7(object):
def __getattr__(self, name):
return None

# глобальная переменная
global_var = 1

# пустая функция
def empty_func():
pass

# доступ к глобальной переменной из функции
def global_var_from_func():
global_var

# доступ к локальной переменной из функции
def local_var_from_func():
local_var = 1
local_var

# сложение целых
a = 1
# int + int
a + a

class A1(object):
def __getattribute__(self, name):
return None

class A2(object):

def get_b(self):
return None

def set_b(self, val):
pass

b = property(get_b, set_b)

class A3(object):
def __init__(self):
self.b = None

class A4(object):
def get_b(self):
return None

b = property(get_b)

class A5(object):
b = None


# создаем иерархию со 128ю уровнями вложенности наследования
# атрибут 'b' будет у "самого базового" класса
# в использовать для доступа будет экзампляр "самого дочернего"

class A6(object):
b = None

for i in range(127):
A6 = type('A6', (A6,), {})

a6 = A6()

class A7(object):
def __getattr__(self, name):
return None

Время замерянно на Core i7-2630QM, 2Ghz. Ubuntu 11.10, python 2.7.2, 64bit

    +----------------------------------------------------------------------------------------+
| Операция | время нс | diff % | относительное | такты CPU |
| показывающая где берется | | | время | |
| aX.b | | | time/(a.b time)| |
|----------------------------------------------------------------------------------------|
| Global var access | 7.4 | 0 | 0.2 | 15 |
| Empty func call | 66.2 | 2 | 2.2 | 132 |
| Global var from func | 9.3 | 0 | 0.3 | 19 |
| Local var from func | 7.4 | 0 | 0.3 | 15 |
| int + int | 17.5 | 1 | 0.6 | 35 |
| A1.__getattribute__(a, 'b') | 178.9 | 1 | 6.0 | 358 |
| A2.b.__get__(a) data property | 139.4 | 0 | 4.7 | 279 |
| a3.__dict__['b'] | 30.0 | 1 | 1.0 | 60 |
| A4.b.__get__(a) property | 140.0 | 1 | 4.7 | 280 |
| A5.__dict__['b'] | 21.6 | 0 | 0.7 | 43 |
| A6.__dict__['b'] | 21.6 | 1 | 0.7 | 43 |
| A7.__getattr__(a, 'b') | 517.0 | 4 | 17.2 | 1034 |
| a.b.b | 54.1 | 2 | 1.8 | 108 |
| a.b.b.b.b | 103.5 | 2 | 3.4 | 207 |
| a....b (128) | 3428.5 | 1 | 114.0 | 6857 |
+----------------------------------------------------------------------------------------+

diff — максимальный разброс времени в % на 10 измерениях, при выброш
енных 2 крайних значениях.

В принципе все результаты, кроме странных «тормозов» __getattr__, ожидаемы. Одинаковое время для A5.__dict__[«b»] и A6.__dict__[«b»] связанно с кешированием привязки имени классового атрибута к классу, так что поиск по всей иерархии не производится. Тестирующий скрипт лежит тут. Тестирование производительности отдельная достаточно большая тема, которой я собираюсь посвятить отдельный пост в ближайшем будущем. Получить указанные тайминги и погрешность на уровне < 5% простым использованием timeit.timeit не получится.

Ссылки:
          github.com/koder-ua/python-lectures/blob/master/attribute_access.py
          github.com/koder-ua/python-lectures/raw/master/attribute.jpg
          docs.python.org/reference/datamodel.html#customizing-attribute-access

Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

Автор: konstantin danilov

libvirt & Co. Облако "на коленке". Часть 1

Buzzword

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

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

Предисловие

На сегодняшний день есть четыре основных облачных системы — перспективный и активно развиваемый openstack, рабочий но мало интересный из-за лицензии eucalyptus, совсем-совсем проприетарный VMware vCloud и очень-очень microsoft azure. Но это все «серьезные» облака, а как это часто бывает большие системы не удобно использовать на малых задачах. Я расскажу как управлять небольшими группами виртуальных машин «малой кровью». Впрочем openstack использует эти же утилиты, а все остальные узнают на чем основываются linux клауды.

Для описанных методик вам необходим Linux 2.6.26+ и процессор с поддержкой виртуализации. Проверить это можно следующими командами:

Hightlited/Raw

$ cat /proc/cpuinfo | egrep 'vmx|svm'
$ cat /proc/cpuinfo | egrep 'rvi|ept'

$ cat /proc/cpuinfo | egrep 'vmx|svm'
$ cat /proc/cpuinfo | egrep 'rvi|ept'

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

Вложенная аппаратная виртуализация не поддерживается, т.е. если linux установлен в виртуальной машине, то описанные примеры работать не будут. Впрочем и те, кто запускает линукс в виртуалке и те, у кого нет поддержки виртуализации могут адаптировать эти примеры для использования xen c паравиртуализацией или lxc — эти техники не требуют аппаратной поддержки. В принципе ипользуемая libvirt имеет зачаточную поддержку windows, желающие могут попробовать и так.

Из других аппаратных требований желательно по-больше оперативной памяти (3Gb+) и быстрый диск (SSD). На магнитном жестком диске все будет работать, но некоторые наиболее интересные варианты организации виртульных образов заметно тормозят на дисковых операциях из-за большого количества разрозненных обращений.

Все примеры для Ubuntu 11.10, для других дистрибутивов нужно подправить обращения к пакетному менеджеру и пути к конфигам.

libvirt

Хотя формально libvirt называется библиотекой, но это целая инфраструктура для управления виртуальными машинами. Она включает:

  • libvirt-bin демон с внешним API, управляющий виртуальными машинами
  • libvirt — библиотека для доступа к демону
  • masqdns — dns/dhcp сервер, используемый совместно с iptables, vlan и бриджами для управлением виртуальными сетями
  • virsh — клиент командной строки

libvirt предоставляет почти унифицированный интерфейс для работы с различными гипервизорами — поддерживаются kvm, lxc, xen, vmware, hyper-v, openvz, и другие — в общем почти все, что еще шевелится. При этом libvirt не пытается подобрать общий знаменатель ко всем системам виртуализации, а предоставляет полный набор возможностей каждого гипервизора — просто не все конфигурации будут работать на всех системах виртуализаций.

Решение предыдущего поста

Если вы не читали предыдущий пост — начните с него.

overloadable включает трассировку и следит за исполнением тела класса. Если обнаруживает, что значение исполняемой переменной было изменено — подменяет ее на объект, управляющий вызовом соответствующей функции в зависимости от параметров.

Hightlited/Raw

# -*- coding:utf8 -*-
import sys

def overloadable():
sys.settrace(OverloadTracer().on_event)
def closure(cls):
# удаляем трассировку по выходу из класса
# вообще говоря это можно сделать по 'return'
# но семантика декоратора заставит использовать
# код по назначению
sys.settrace(None)
return cls
return closure

class OverloadTracer(object):

def __init__(self):
self.in_class = False
self.prev_locals = {}

def on_event(self, frame, event, _):
#вызывается при каждом событии трассировки
if event == 'return':
# возврат из блока
self.update_locals(frame.f_locals)
elif event == 'call':
# вызов - открытие блока
# не заходим во вложенные вызовы
if self.in_class:
return None
self.in_class = True
else:
self.update_locals(frame.f_locals)
return self.on_event

def update_locals(self, loc):
# сравниваем loc с self.prev_locals
for name, prev_val in self.prev_locals.items():
if name in loc:
# если находим перекрытие функции - подменяем ее на объект,
# управляющий перегрузкой
if loc[name] is not prev_val and
( callable(prev_val) or
isinstance(prev_val, OverloadableFunc) )and
callable(loc[name]):
loc[name] = self.overload(prev_val, loc[name])

# делаем копию текущего состояния тела класса
self.prev_locals = loc.copy()

@staticmethod
def overload(prev_val, curr_val):

if not isinstance(prev_val, OverloadableFunc):
overld = OverloadableFunc()
overld.add(prev_val)
prev_val = overld

prev_val.add(curr_val)
return prev_val

# класс имитирует функцию с одним аргументом
# и по его типу выбирает соответствующий зарегистрированный обработчик
class OverloadableFunc(object):
def __init__(self):
self.funcs = {}

def __get__(self, obj, cls):
# для имитации метода объект этого типа
# должен быть свойством
def closure(val):
return self.funcs[type(val).__name__](obj, val)
return closure

def add(self, func):
# добавляем новую функцию-обработчик
tp_name = func.__doc__.strip().split('n')[0]
self.funcs[tp_name] = func

# использование в предыдущем посте

# -*- coding:utf8 -*-
import sys

def overloadable():
sys.settrace(OverloadTracer().on_event)
def closure(cls):
# удаляем трассировку по выходу из класса
# вообще говоря это можно сделать по 'return'
# но семантика декоратора заставит использовать
# код по назначению
sys.settrace(None)
return cls
return closure

class OverloadTracer(object):

def __init__(self):
self.in_class = False
self.prev_locals = {}

def on_event(self, frame, event, _):
#вызывается при каждом событии трассировки
if event == 'return':
# возврат из блока
self.update_locals(frame.f_locals)
elif event == 'call':
# вызов - открытие блока
# не заходим во вложенные вызовы
if self.in_class:
return None
self.in_class = True
else:
self.update_locals(frame.f_locals)
return self.on_event

def update_locals(self, loc):
# сравниваем loc с self.prev_locals
for name, prev_val in self.prev_locals.items():
if name in loc:
# если находим перекрытие функции - подменяем ее на объект,
# управляющий перегрузкой
if loc[name] is not prev_val and
( callable(prev_val) or
isinstance(prev_val, OverloadableFunc) )and
callable(loc[name]):
loc[name] = self.overload(prev_val, loc[name])

# делаем копию текущего состояния тела класса
self.prev_locals = loc.copy()

@staticmethod
def overload(prev_val, curr_val):

if not isinstance(prev_val, OverloadableFunc):
overld = OverloadableFunc()
overld.add(prev_val)
prev_val = overld

prev_val.add(curr_val)
return prev_val

# класс имитирует функцию с одним аргументом
# и по его типу выбирает соответствующий зарегистрированный обработчик
class OverloadableFunc(object):
def __init__(self):
self.funcs = {}

def __get__(self, obj, cls):
# для имитации метода объект этого типа
# должен быть свойством
def closure(val):
return self.funcs[type(val).__name__](obj, val)
return closure

def add(self, func):
# добавляем новую функцию-обработчик
tp_name = func.__doc__.strip().split('n')[0]
self.funcs[tp_name] = func

# использование в предыдущем посте

Поскольку тело класса исполняется то трассировщик может «видеть» как в него добавляются новые методы или подменяются старые и использовать эту информацию что-бы произвести перегрузку функций. На всякий случай — код класса исполняется только один раз — при импорте текущего модуля. По выходу из создания класса трассировка выключается и не оказывает никакого влияния на его инстанцирование или вызов методов.

Этот код можно расширить до перегрузки функций (не методов) и добавить поддержку нескольких параметров.

Идея с использованием трассировки для расширения синтаксиса была реализована как минимум в одной широко известной библиотеке — PEAK.util.decorator. Она позволяет использовать декораторы в python до 2.4.

Ссылки:
          pypi.python.org/pypi/DecoratorTools

Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

Автор: konstantin danilov

Связные списки

В информатике, свя́зный спи́сок — структура данных, состоящая из узлов, каждый из которых содержит как собственные данные, так и одну или две ссылки («связки») на следующий и/или предыдущий узел списка. Принципиальным преимуществом перед массивом является структурная гибкость: порядок элементов связного списка может не совпадать с порядком расположения элементов данных в памяти компьютера, а порядок обхода списка всегда явно задаётся его внутренними связями.

Мы с вами рассмотрим реализацию односвязного (однонаправленного) списка.
*-Нравится статья? Кликни по рекламе! 🙂


 
Коли уж я натолкнулся на прелестные статьи Сергея Яковлева, на сайте IBM, которые люди так не заслужено оценили в 2 звезды, давайте обсудим их.
В конечном итоге мы получим реализацию данного рисунка

В одной из статей Sython’а (Cвойство замыкания, на примере list) мы уже создавали похожую структуру, методами функционального программирования. Пришло время задействовать ООП!)

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

Рисунок 1. Пример связного списка

У последней записи на рисунке отсутствует ссылка, но она есть и указывает на NULL. На основе списков можно реализовать структуры данных, такие как стеки, очереди и ассоциативные массивы.
Ранее уже упоминалось, что в массиве все элементы расположены в памяти по порядку, а в списке они в памяти никак не упорядочены. Более того, элемент может быть добавлен в любую позицию в списке. Однако сама операция по вставке элемента в список требует дополнительных ресурсов, так как нужно последовательно просканировать список для поиска нужной позиции.

Основные правила реализации связных списков
Список состоит из элементов, называемых узлами (node). Первый узел списка называется «головным» (head), а последний — «хвостовым» (tail). На рисунке 2 изображен двойной связный список.

Рисунок 2. Двойной связный список

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

Об уровнях абстракций — The Very Last API

При написании не тривиальных приложений возникает вопрос: над какими библиотеками делать еще один абстрактный слой, а над какими — нет? Какие абстракции делать?

Стоит ли делать прослойку над, например, SQLAlchemy? Это же и так прослойка над SQL и DBAPI. Имеет ли смысл делать уровни абстракций над такими достаточно хорошими и отточенными в смысле интерфейсов библиотеками?

Ответ очень простой — библиотеки представляют API который должен быть применим для широкого спектра приложений. Они отображают низкоуровневые (с точки зрения их API ) вызовы на более высокоуровневый, но абстрактный интерфейс. Характерный пример — библиотеки передачи сообщений. Они позволяют не думать о сокетах, упаковке/распаковке float/int и т.п., а просто передавать структуры данных.

Типичный API системы пересылки сообщений выглядит как:

class Messaging(object):
def send_message_async(self, dest, message_tp, message_data):
# some code

def send_message_sync(self, dest, message_tp, message_data):
# some code

def get_message(self):
# some code

Но программе не нужно посылать никакие сообщения! Ей нужно выполнить действия — показать пользователю GUI, узнать завершился ли удаленный процесс, etc. API сообщений, которое было-бы идеально для некоторой программы выглядит примерно так:

class MyAPI(object):

@exception_on_false
def show_ui_message(self, level, text):
return self.messanger.send_message_async(self.UI_PROC_ID,
SHOW_DIALOG,
dict(level=level, text=text))

@exception_on_false
def reboot_vm(self, ip):
return self.messanger.send_message_async(
self.get_remote_agent_id(ip),
REBOOT_VM,
None)

def ls_remote(self, ip, remote_path):
tp, res = self.messanger.send_message_sync(
self.get_remote_agent_id(ip),
EXEC_CMD,
'ls -l {0}'.format(remote_path))
if tp == EXECUTION_FINISHED_OK:
return res
else:
raise RuntimeError("Cmd ... finished with error code {0}".
format(res))

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

Это особенная прослойка, это «последняя линия». Если остальные API предоставляют нам абстракции, то эта прослойка не должна добавлять ничего лишнего, она избавляет нас от более не нужных абстракций и говорит языком предметной области программы.

Вам нужно хранить в базе список фруктов? Сделайте функцию store_fruits. Такая функция позволить вам перейти от PostgreSQL к Cassandra, а потом к текстовым файлам (маловероятная ситуация, но не суть) без влияния на остальную программу. Потому что программе все равно где лежат данные. Программу интересует только что они сохраняются и восстанавливаются.

Мы никак не может защититься от изменения требований к программе и вместе с изменениями требований нужно будет меняться и API, который предоставляет наш слой абстракции. Но вот изменения в типе базы/структуре базы/ORM не будет приводить к изменению кода. Если смена БД или ORM — маловероятная ситуация, то вот добавление нового поля вида deleted, означающего, что запись вроде как удалена и почти нигде не должна использоваться — весьма частый случай.

# почти реальный запрос прямо из функции, отвечающей за логики программы 

services = session.query(Service).
filter(Service.zone_id == zone_id).
filter(Service.service_id == service_id).
with_lockmode('update').
limit(10).all()
# Чего-чего?????
# комментарий к запросу немного бы спас ситуацию
# но вместо решения проблем с помощью комментирования их лучше не создавать
# Этот код не требует комментариев

for service in db.get_10_services(zone_id, service_id):
# some code


# в файле db.py
def get_10_services(self, zone_id, service_id):
return self.session.query(Service).
filter(Service.zone_id == zone_id).
filter(Service.service_id == service_id).
with_lockmode('update').
limit(10).all()

Еще одна ошибка — попытка сэкономить на таком API и сделать в этом духе:

import sqlalchemy as sa

# one Function to rule them all!!
def get_user(session, *opts):
return session.query(User).filter(sa.and_(*opts)).all()

# этот код уже требует знания что там у нас за sqlalchemy такая
# и на mongo его уже не переписать так-же легко

# тут "торчат уши" sqlalchemy. Да, мы съекономили 10-20 нажатий клавишь
# на каждый вызов, но это не "the very last API"

for user in get_user(User.name == 'vasya', User.age > datetime.now()):
pass

Безусловно у любого абстрагирования есть минимум один существенный минус — пользуясь им люди хуже понимают что происходит на уровнях ниже. При возникновении проблем внутри абстракций или еще ниже (например могут быть проблемы с сетью) на их решение может уйти много времени — а проблемы возникают постоянно. Во-вторых исчезает контроль над ситуацией. Любая сложная библиотека несет свои неожиданности в добавок к особенностям нижнего уровня. В итоге вопрос «почему функция выборки из базы зависает» может стать не решаемым.

Я за последнее время видел некоторое количество достаточно опытных программистов, которые почти ничего не знают про сокеты и TCP, потому что RabbitMQ и про потоки, потому что фреймфорк подставьтет-тут-свой-фреймфорк. Нет, это не проявления вселенского зла. Отлично что программирование упрощается, но эта категория программистов — клиенты обеих проблем сверху.

Впрочем это уже другой вопрос. А наш вопрос — абстракции :).

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

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

Ссылки:
          www.sqlalchemy.org/
          www.python.org/dev/peps/pep-0249/
Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua.При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

Автор: konstantin danilov

Раскраска графа

Рассмотрим математическую модель, используемую для управления
светофорами на сложном перекрестке дорог. Мы должны создать программу, которая в
качестве входных данных использует множество всех допустимых поворотов на перекрестке (продолжение прямой дороги, проходящей через перекресток, также будем
считать «поворотом») и разбивает это множество на несколько групп так, чтобы все повороты в группе могли выполняться одновременно, не создавая проблем друг для друга. Затем мы сопоставим с каждой группой поворотов соответствующий режим работы
светофоров на перекрестке. Читать