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;">&nbsp;&nbsp;&nbsp; def __get__(self, obj, objtype):</span>
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &quot;__get__ from instance %s, type %s&quot; % (obj, type)</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &quot;foo&quot;</span><br style="font-family: courier new,monospace;"><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">
&nbsp;&nbsp;&nbsp; def __set__(self, obj, value):</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print &quot;__set__ on instance %s, value %s&quot; % (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;">
&nbsp;&nbsp;&nbsp; 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&#39;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>&nbsp; Because of this problem, I may have to install properties into a class&#39;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> &lt;<a href="mailto:aahz@pythoncraft.com">aahz@pythoncraft.com</a>&gt; 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>&gt;<br>&gt; Python, probably through the valid assumption that most attribute<br>&gt; lookups go to the class, tries to look for the attribute in the class<br>&gt; first, and in the instance, second.
<br>&gt;<br>&gt; What Python currently does is quite peculiar!<br>&gt; Here&#39;s a short description o PyObject_GenericGetAttr:<br>&gt;<br>&gt; A. Python looks for a descriptor in the _entire_ mro hierarchy<br>&gt; (len(mro) class/type check and dict lookups).
<br>&gt; B. If Python found a descriptor and it has both get and set functions<br>&gt; - it uses it to get the value and returns, skipping the next stage.<br>&gt; C. If Python either did not find a descriptor, or found one that has
<br>&gt; no setter, it will try a lookup in the instance dict.<br>&gt; D. If Python failed to find it in the instance, it will use the<br>&gt; descriptor&#39;s getter, and if it has no getter it will use the<br>&gt; 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.&nbsp;&nbsp;As other people have noted, the current semantics cannot be
<br>changed.&nbsp;&nbsp;I&#39;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&#39;t particularly<br>comprehensible).&nbsp;&nbsp;Here they are for the record (from 3/25/2003):<br><br>&#39;&#39;&#39;<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.&nbsp;&nbsp;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&#39;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>&nbsp;&nbsp;&nbsp;&nbsp;* an attribute is added or deleted to the class or its base classes
<br><br>&nbsp;&nbsp;&nbsp;&nbsp;* an attribute has its status changed to or from being a data<br>&nbsp;&nbsp;&nbsp;&nbsp;descriptor<br><br>This file uses Python pseudocode to describe changes necessary to<br>implement CACHE_ATTR at the C level.&nbsp;&nbsp;Except for class Meta, these are
<br>all exact descriptions of the work being done.&nbsp;&nbsp;Except for class Meta the<br>changes go into object.c (Meta goes into typeobject.c).&nbsp;&nbsp;The pseudocode<br>looks somewhat C-like to ease the transformation.<br>&#39;&#39;&#39;
<br><br>NULL = object()<br><br>def getattr(inst, name):<br>&nbsp;&nbsp;&nbsp;&nbsp;isdata, where = lookup(inst.__class__, name)<br>&nbsp;&nbsp;&nbsp;&nbsp;if isdata:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descr = where[name]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if hasattr(descr, &quot;__get__&quot;):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return descr.__get__(inst)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return descr<br>&nbsp;&nbsp;&nbsp;&nbsp;value = inst.__dict__.get(name, NULL)<br>&nbsp;&nbsp;&nbsp;&nbsp;if value != NULL:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return value<br>&nbsp;&nbsp;&nbsp;&nbsp;if where == NULL:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise AttributError<br>&nbsp;&nbsp;&nbsp;&nbsp;descr = where[name]
<br>&nbsp;&nbsp;&nbsp;&nbsp;if hasattr(descr, &quot;__get__&quot;):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = descr.__get__(inst)<br>&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = descr<br>&nbsp;&nbsp;&nbsp;&nbsp;return value<br><br>def setattr(inst, name, value):<br>&nbsp;&nbsp;&nbsp;&nbsp;isdata, where = lookup(inst.__class__, name)
<br>&nbsp;&nbsp;&nbsp;&nbsp;if isdata:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descr = where[name]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descr.__set__(inst, value)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return<br>&nbsp;&nbsp;&nbsp;&nbsp;inst.__dict__[name] = value<br><br>def lookup(cls, name):<br>&nbsp;&nbsp;&nbsp;&nbsp;if cls.__cache__ != NULL:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pair = cls.__cache__.get(name)
<br>&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pair = NULL<br>&nbsp;&nbsp;&nbsp;&nbsp;if pair:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return pair<br>&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for c in cls.__mro__:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;where = c.__dict__<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if name in where:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descr = where[name]
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;isdata = hasattr(descr, &quot;__set__&quot;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pair = isdata, where<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pair = False, NULL<br>&nbsp;&nbsp;&nbsp;&nbsp;if cls.__cache__ != NULL:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cls.__cache__[name] = pair
<br>&nbsp;&nbsp;&nbsp;&nbsp;return pair<br><br><br>&#39;&#39;&#39;<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>&#39;&#39;&#39;<br><br>from types import ClassType<br><br>class Meta(type):<br>&nbsp;&nbsp;&nbsp;&nbsp;def _invalidate(cls, name):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if name in cls.__cache__:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;del cls.__cache__[name]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for c in cls.__subclasses__():
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if name not in c.__dict__:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self._invalidate(c, name)<br>&nbsp;&nbsp;&nbsp;&nbsp;def _build_cache(cls, bases):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for base in bases:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if type(base.__class__) is ClassType:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cls.__cache__ = NULL
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cls.__cache__ = {}<br>&nbsp;&nbsp;&nbsp;&nbsp;def __new__ (cls, bases):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self._build_cache(cls, bases)<br>&nbsp;&nbsp;&nbsp;&nbsp;def __setbases__(cls, bases):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self._build_cache(cls, bases)
<br>&nbsp;&nbsp;&nbsp;&nbsp;def __setattr__(cls, name, value):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if cls.__cache__ != NULL:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;old = cls.__dict__.get(name, NULL)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wasdata = old != NULL and hasattr(old, &quot;__set__&quot;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;isdata = value != NULL and hasattr(value, &quot;__set__&quot;)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if wasdata != isdata or (old == NULL) != (value === NULL):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self._invalidate(cls, name)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type.__setattr__(cls, name, value)<br>&nbsp;&nbsp;&nbsp;&nbsp;def __delattr__(cls, name):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.__setattr__(cls, name, NULL)
<br>--<br>Aahz (<a href="mailto:aahz@pythoncraft.com">aahz@pythoncraft.com</a>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;*&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.pythoncraft.com/">http://www.pythoncraft.com/</a><br><br>&quot;as long as we like the same operating system, things are cool.&quot; --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>&quot;The universe is always one step beyond logic.&quot; -- Frank Herbert