Pull to refresh

Comments 20

Замечательно, рассматриваете уже более глубокие вещи, спасибо.
К сожалению, руки не дошли до многих вкусных вещей: дескрипторов, наследования — алгоритм C3, абстрактных базовых классов, слотов, отличия __getattr__ и __getattribute__ и прочее.
Пожалуйста, добавьте ссылки на остальные части статьи
Очень хорошо преподносите материал. Все очень понятно. Огромнейшее спасибо!
Объяаните это:
>>> class A(object): pass
>>> class B(object): pass

>>> a=A()

>>> a.__class__
<class '__main__.A'>

>>> a.__class__=B

>>> a.__class__
<class '__main__.B'>

>>> a.__class__={}.__class__
TypeError                                 Traceback (most recent call last)
/home/seriy/<ipython console> in <module>()
TypeError: __class__ assignment: only for heap types

>>> a.__class__=dict
TypeError                                 Traceback (most recent call last)
/home/seriy/<ipython console> in <module>()
TypeError: __class__ assignment: only for heap types

Т.е. почему нельзя а качестве класса задавать встроенные типы?
Потому что реально за присвоение специальных атрибутов отвечает класс, и ментальная модель «класс можно поменять» несколько отличается от того, как это все происходит на самом деле. На самом деле это вот так:

static int
object_set_class(PyObject *self, PyObject *value, void *closure)
{
PyTypeObject *oldto = Py_TYPE(self);
PyTypeObject *newto;

if (value == NULL) {
PyErr_SetString(PyExc_TypeError,
"can't delete __class__ attribute");
return -1;
}
if (!PyType_Check(value)) {
PyErr_Format(PyExc_TypeError,
"__class__ must be set to new-style class, not '%s' object",
Py_TYPE(value)->tp_name);
return -1;
}
newto = (PyTypeObject *)value;
if (!(newto->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
!(oldto->tp_flags & Py_TPFLAGS_HEAPTYPE))
{
PyErr_Format(PyExc_TypeError,
"__class__ assignment: only for heap types");
return -1;
}
if (compatible_for_assignment(newto, oldto, "__class__")) {
Py_INCREF(newto);
Py_TYPE(self) = newto;
Py_DECREF(oldto);
return 0;
}
else {
return -1;
}
}


Прежде чем как-то поменять __class__ происходит несколько проверок. И если старый класс и новый более или менее «однотипны», то все — ок, иначе — будет ругаться.
Ок, поясню почему спросил. Нужно было реализовать ленивые вычисления. Для этого на месте результата оствалял объекты-заглушки и выполнял расчеты только если к ним обратились. Например ленивое считывание файла или HTTP запрос и возврат JSON объекта. И нужно было сделать максимально универсльный ленивый загрузчик (чтоб умел маскировать объекты любого типа — хоть какой-либо класс, хоть словарь, хоть список).

На примере JSON полученного по HTTP:

http://example.com/user_%{user_id}d.json
возвращает либо JSON список либо JSON объект для юзера user_id. Нам нужно вернуть клиенту список загруженных профилей, но из них будет использоваться только один-два, неизвестно какие.

При решении в лоб код выглядит так:

def get_profiles(user_ids):
    profiles=[]
    for id in user_ids:
        profile=json.decode(urllib.open("http://example.com/user_%d.json"%id).read())
        profukes.append(profile)
    return profiles


И все профили будут загружены, независимо от того, используются они или нет. Хочется следать так, чтобы загрузка происходила только в момент обращения (тут есть подводные камни для коиента типа неожиданно выскакивающих exception за try блоком, но опустим это). Для этого на место profile подставляется заглушка, загружающая данные в момент первого обращения

def get_profiles_lazy(user_ids):
    profiles=[]
    for id in user_ids:
        profile=lazy_loader(id)
        profukes.append(profile)
    return profiles


Как мне реализовать lazy_loader чтобы клиент не смог отличить результаты от первого и второго кода? Например
res1=get_profiles_lazy(user_ids)
res2=get_profiles(user_ids)
assert type(res1[1]) == type(res2[1])
assert res1[1].field == res2[1].field


и т.п.?

Сразу скажу — у меня получилось через

class lazy_loader(object):
  def _res(self):
    if not self._result:
      self._result=json.decode(urllib.open("http://example.com/user_%d.json"%self.id).read())
    return self._result

  def __getattribute__(self, name):
    if name in ['_result', '_res', 'master', 'id']:#don't proxy requests for own properties
      return super(lazy_mask, self).__getattribute__(name)
    else:#but proxy requests for masked object
      return self._res().__getattribute__(name)


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

Но лично мне решение кажется не совсем верным, хочется не проксировать обращения а полностью заменить себя загруженным объектом…
Про выражение yield знаете? Оно как раз предназначено для ленивых вычислений.
>>> import urllib2
>>> uri = 'http://habrahabr.ru/blogs/t/{0}'
>>> def lazy(article_id):
...     yield urllib2.urlopen(uri.format(article_id))
... 
>>> b = lazy(114590)
>>> b
<generator object lazy at 0x10256f780>  # URI еще не открывалось
>>> next(b)  # Открываем
<addinfourl at 4334244568 whose fp = <socket._fileobject object at 0x10256b750>>

Канеш знаю. Но тут прозрачность нарушается — нужно явно вызвать next() и работать с тем, что оно вернет.
Соответственно предложенные мной assert-ы не сработают без изменения кода клиента.
И встроенные типы являются классами. От встроенных типов, например, можно наследоваться:

>>> Dict = {}.__class__
>>> class Foo(Dict):
...     pass
... 
>>> = Foo()
>>> f['herp']='derp'
>>> f
{'herp': 'derp'}
>>> 
но наследование не работает в рантайме. Сейчас развернутый пример сделаю
Первая строчка не нужна, класс dict входит в __builtins__.
Более того object, и type — это объекты типа (классы), и у них тоже есть специальные атрибуты __name__, __bases___


Для понимания объектной модели python нужно обозначить два вида отношений: subclass-superclass (выражает частное-общее, например «человек» это «живое существо») и instance-type (выражает определение, «Таня» это «человек»). В русском (да и английском) языках, данные отношения обычно называют словом «это» (или «является»), поэтому

… в тексте они неразличимы. При попытке объяснить, начинается блуждание в трех соснах. :) На самом деле object и type _это_ действительно объекты, но по-разному. :) object это объект потому, что он является экземпляром type, а type это объект, потому что он является подклассом object.

Добавьте ссылку: www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html — классическое чтиво по этой теме, объясняющее объектную модель python на простых примерах с картинками.
Добавил. Отличная ссылка.
Only those users with full accounts are able to leave comments. Log in, please.