Архив автора: admin

Как это сделано?

Отсутствие перегрузки функций — это то что мне всегда не нравилось в python. Не то что бы без них невозможно было жить, да и виртуальные методы типа __len__ сглаживают проблему, но все-таки. И пусть есть PEAK.rules, но его синтаксис всегда раздражал. Ну вот как можно без боли смотреть на это:

Hightlited/Raw

from peak.rules import abstract, when

@abstract()
def pprint(ob):
"""A pretty-printing generic function"""

@when(pprint, (list,))
def pprint_list(ob):
print "pretty-printing a list"

@when(pprint, (int,))
def pprint_int(ob):
print "pretty-printing an integer"

#......

from peak.rules import abstract, when

@abstract()
def pprint(ob):
"""A pretty-printing generic function"""

@when(pprint, (list,))
def pprint_list(ob):
print "pretty-printing a list"

@when(pprint, (int,))
def pprint_int(ob):
print "pretty-printing an integer"

#......

Во-первых опять нужно придумывать для каждого типа свои имена функций, во-вторых не по-питоновски много лишних нажатий клавиш, даже в С++ это — лишнее: @when(pprint, ( :).

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

Ну собственно угадайте как работает написанное ниже (какая магия зашита в method_overloader.overloadable):

Hightlited/Raw

from method_overloader import overloadable

@overloadable()
class A(object):
def overloaded_func(self, x):
"int"
return "Integer func called {0}".format(x)

def overloaded_func(self, x):
"str"
return "String func called {0!r}".format(x)

def overloaded_func(self, x):
"float"
return "Float func called {0!r}".format(x)

def overloaded_func(self, x):
"list"
return "List func called {0!r}".format(x)

t = A()

print "t.overloaded_func(1) =", t.overloaded_func(1)
print "t.overloaded_func('asas') =", t.overloaded_func("asas")
print "t.overloaded_func(1.1) =", t.overloaded_func(1.1)
print "t.overloaded_func([1, 2, 3]]) =", t.overloaded_func([1, 2, 3])

from method_overloader import overloadable

@overloadable()
class A(object):
def overloaded_func(self, x):
"int"
return "Integer func called {0}".format(x)

def overloaded_func(self, x):
"str"
return "String func called {0!r}".format(x)

def overloaded_func(self, x):
"float"
return "Float func called {0!r}".format(x)

def overloaded_func(self, x):
"list"
return "List func called {0!r}".format(x)

t = A()

print "t.overloaded_func(1) =", t.overloaded_func(1)
print "t.overloaded_func('asas') =", t.overloaded_func("asas")
print "t.overloaded_func(1.1) =", t.overloaded_func(1.1)
print "t.overloaded_func([1, 2, 3]]) =", t.overloaded_func([1, 2, 3])

Запускаем —

    .........$ python tracer.py 
t.overloaded_func(1) = Integer func called 1
t.overloaded_func('asas') = String func called 'asas'
t.overloaded_func(1.1) = Float func called 1.1
t.overloaded_func([1, 2, 3]]) = List func called [1, 2, 3]

Все это на обычном python без подмены механизма импорта, без ковыряния в ast и т.п. Ответы можно на koder.mail@gmail.com.

P.S. Если что — в python2.X метаклассе невозможно узнать что происходило в теле класса, можно только узнать что вышло в итоге, т.е.:

Hightlited/Raw

class M(object):
s = 1
s = 2

class M(object):
s = 1
s = 2

в метакласс класса прийдет в качестве словаря класса {s : 2} и узнать что еще было s = 1 в метаклассе нельзя.

Ссылки:
          pypi.python.org/pypi/PEAK-Rules
          blip.tv/pycon-us-videos-2009-2010-2011
          docs.python.org/library/ast.html

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

Автор: konstantin danilov

Интерфейсы в python или "Предъявите документы!"

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

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

Оператор with

Теория

Оператор with появился в python 2.5, но, не смотря на это, используется до сих пор недостаточно широко. Являясь упрощенной версией анонимных блоков кода with позволяет:

  • исполнить код до начала блока
  • исполнить код по выходу из блока, независимо от того это выход по исключению с помощью return или другим способом
  • обработать исключение, возникшее в блоке.

Читать

Динамические атрибуты в 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

Правда о человеческих отношениях

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

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

праздничный декор на Новый год

Праздничный декор на Новый год

Сегодня нашей темой снова будет праздничный декор на Новый год. Декорировать будем стеклянную подставку под свечи в технике декупаж и пуговичный шарик.

праздничный декор на Новый год

праздничный декор на Новый год

Читать