While you're at it, it would be nice to fix this ugly asymmetry I found in descriptors. It seems that descriptor's __get__ is called even when accessed from a class rather than instance, but __set__ is only invoked from instances, never from classes:
<br><br><span style="font-family: courier new,monospace;">class Descr(object):</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> def __get__(self, obj, objtype):</span>
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> print "__get__ from instance %s, type %s" % (obj, type)</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> return "foo"</span><br style="font-family: courier new,monospace;"><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">
def __set__(self, obj, value):</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> print "__set__ on instance %s, value %s" % (obj, value)</span><br style="font-family: courier new,monospace;">
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">class Foo(object):</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">
foo = Descr()</span><br style="font-family: courier new,monospace;"><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">print Foo.foo # works</span><br style="font-family: courier new,monospace;">
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">## doesn't work, goes directly to the class dict, not calling __set__</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;">Foo.foo = 123</span><br style="font-family: courier new,monospace;"><br> Because of this problem, I may have to install properties into a class's metaclass achieve the same effect that I expected to achieve with a simple descriptor :-(
<br><br><br><div><span class="gmail_quote">On 10/06/07, <b class="gmail_sendername">Aahz</b> <<a href="mailto:aahz@pythoncraft.com">aahz@pythoncraft.com</a>> wrote:</span><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
On Sun, Jun 10, 2007, Eyal Lotem wrote:<br>><br>> Python, probably through the valid assumption that most attribute<br>> lookups go to the class, tries to look for the attribute in the class<br>> first, and in the instance, second.
<br>><br>> What Python currently does is quite peculiar!<br>> Here's a short description o PyObject_GenericGetAttr:<br>><br>> A. Python looks for a descriptor in the _entire_ mro hierarchy<br>> (len(mro) class/type check and dict lookups).
<br>> B. If Python found a descriptor and it has both get and set functions<br>> - it uses it to get the value and returns, skipping the next stage.<br>> C. If Python either did not find a descriptor, or found one that has
<br>> no setter, it will try a lookup in the instance dict.<br>> D. If Python failed to find it in the instance, it will use the<br>> descriptor's getter, and if it has no getter it will use the<br>> descriptor itself.
<br><br>Guido, Ping, and I tried working on this at the sprint for PyCon 2003.<br>We were unable to find any solution that did not affect critical-path<br>timing. As other people have noted, the current semantics cannot be
<br>changed. I'll also echo other people and suggest that this discusion be<br>moved to python-ideas if you want to continue pushing for a change in<br>semantics.<br><br>I just did a Google for my notes from PyCon 2003 and it appears that I
<br>never sent them out (probably because they aren't particularly<br>comprehensible). Here they are for the record (from 3/25/2003):<br><br>'''<br>CACHE_ATTR is the name used to describe a speedup (for new-style classes
<br>only) in attribute lookup by caching the location of attributes in the<br>MRO. Some of the non-obvious bits of code:<br><br>* If a new-style class has any classic classes in its bases, we<br>can't do attribute caching (we need to weakrefs to the derived
<br>classes).<br><br>* If searching the MRO for an attribute discovers a data descriptor (has<br>tp_descr_set), that overrides any attribute that might be in the instance;<br>however, the existence of tp_descr_get still permits the instance to
<br>override its bases (but tp_descr_get is called if there is no instance<br>attribute).<br><br>* We need to invalidate the cache for the updated attribute in all derived<br>classes in the following cases:<br><br> * an attribute is added or deleted to the class or its base classes
<br><br> * an attribute has its status changed to or from being a data<br> descriptor<br><br>This file uses Python pseudocode to describe changes necessary to<br>implement CACHE_ATTR at the C level. Except for class Meta, these are
<br>all exact descriptions of the work being done. Except for class Meta the<br>changes go into object.c (Meta goes into typeobject.c). The pseudocode<br>looks somewhat C-like to ease the transformation.<br>'''
<br><br>NULL = object()<br><br>def getattr(inst, name):<br> isdata, where = lookup(inst.__class__, name)<br> if isdata:<br> descr = where[name]<br> if hasattr(descr, "__get__"):<br> return descr.__get__(inst)
<br> else:<br> return descr<br> value = inst.__dict__.get(name, NULL)<br> if value != NULL:<br> return value<br> if where == NULL:<br> raise AttributError<br> descr = where[name]
<br> if hasattr(descr, "__get__"):<br> value = descr.__get__(inst)<br> else:<br> value = descr<br> return value<br><br>def setattr(inst, name, value):<br> isdata, where = lookup(inst.__class__, name)
<br> if isdata:<br> descr = where[name]<br> descr.__set__(inst, value)<br> return<br> inst.__dict__[name] = value<br><br>def lookup(cls, name):<br> if cls.__cache__ != NULL:<br> pair = cls.__cache__.get(name)
<br> else:<br> pair = NULL<br> if pair:<br> return pair<br> else:<br> for c in cls.__mro__:<br> where = c.__dict__<br> if name in where:<br> descr = where[name]
<br> isdata = hasattr(descr, "__set__")<br> pair = isdata, where<br> break<br> else:<br> pair = False, NULL<br> if cls.__cache__ != NULL:<br> cls.__cache__[name] = pair
<br> return pair<br><br><br>'''<br>These changes go into typeobject.c; they are not a complete<br>description of what happens during creation/updates, only the<br>changes necessary to implement CACHE_ATTRO.
<br>'''<br><br>from types import ClassType<br><br>class Meta(type):<br> def _invalidate(cls, name):<br> if name in cls.__cache__:<br> del cls.__cache__[name]<br> for c in cls.__subclasses__():
<br> if name not in c.__dict__:<br> self._invalidate(c, name)<br> def _build_cache(cls, bases):<br> for base in bases:<br> if type(base.__class__) is ClassType:<br> cls.__cache__ = NULL
<br> break<br> else:<br> cls.__cache__ = {}<br> def __new__ (cls, bases):<br> self._build_cache(cls, bases)<br> def __setbases__(cls, bases):<br> self._build_cache(cls, bases)
<br> def __setattr__(cls, name, value):<br> if cls.__cache__ != NULL:<br> old = cls.__dict__.get(name, NULL)<br> wasdata = old != NULL and hasattr(old, "__set__")<br> isdata = value != NULL and hasattr(value, "__set__")
<br> if wasdata != isdata or (old == NULL) != (value === NULL):<br> self._invalidate(cls, name)<br> type.__setattr__(cls, name, value)<br> def __delattr__(cls, name):<br> self.__setattr__(cls, name, NULL)
<br>--<br>Aahz (<a href="mailto:aahz@pythoncraft.com">aahz@pythoncraft.com</a>) <*> <a href="http://www.pythoncraft.com/">http://www.pythoncraft.com/</a><br><br>"as long as we like the same operating system, things are cool." --piranha
<br>_______________________________________________<br>Python-Dev mailing list<br><a href="mailto:Python-Dev@python.org">Python-Dev@python.org</a><br><a href="http://mail.python.org/mailman/listinfo/python-dev">http://mail.python.org/mailman/listinfo/python-dev
</a><br>Unsubscribe: <a href="http://mail.python.org/mailman/options/python-dev/gjcarneiro%40gmail.com">http://mail.python.org/mailman/options/python-dev/gjcarneiro%40gmail.com</a><br></blockquote></div><br><br clear="all">
<br>-- <br>Gustavo J. A. M. Carneiro<br>INESC Porto, Telecommunications and Multimedia Unit<br>"The universe is always one step beyond logic." -- Frank Herbert