[Python-Dev] Instance variable access and descriptors (original) (raw)

Gustavo Carneiro gjcarneiro at gmail.com
Mon Jun 11 12:43:16 CEST 2007


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:

class Descr(object): def get(self, obj, objtype): print "get from instance %s, type %s" % (obj, type) return "foo"

def __set__(self, obj, value):
    print "__set__ on instance %s, value %s" % (obj, value)

class Foo(object): foo = Descr()

print Foo.foo # works

doesn't work, goes directly to the class dict, not calling set

Foo.foo = 123

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 :-(

On 10/06/07, Aahz <aahz at pythoncraft.com> wrote:

On Sun, Jun 10, 2007, Eyal Lotem wrote: > > Python, probably through the valid assumption that most attribute > lookups go to the class, tries to look for the attribute in the class > first, and in the instance, second. > > What Python currently does is quite peculiar! > Here's a short description o PyObjectGenericGetAttr: > > A. Python looks for a descriptor in the entire mro hierarchy > (len(mro) class/type check and dict lookups). > B. If Python found a descriptor and it has both get and set functions > - it uses it to get the value and returns, skipping the next stage. > C. If Python either did not find a descriptor, or found one that has > no setter, it will try a lookup in the instance dict. > D. If Python failed to find it in the instance, it will use the > descriptor's getter, and if it has no getter it will use the > descriptor itself. Guido, Ping, and I tried working on this at the sprint for PyCon 2003. We were unable to find any solution that did not affect critical-path timing. As other people have noted, the current semantics cannot be changed. I'll also echo other people and suggest that this discusion be moved to python-ideas if you want to continue pushing for a change in semantics. I just did a Google for my notes from PyCon 2003 and it appears that I never sent them out (probably because they aren't particularly comprehensible). Here they are for the record (from 3/25/2003): ''' CACHEATTR is the name used to describe a speedup (for new-style classes only) in attribute lookup by caching the location of attributes in the MRO. Some of the non-obvious bits of code: * If a new-style class has any classic classes in its bases, we can't do attribute caching (we need to weakrefs to the derived classes). * If searching the MRO for an attribute discovers a data descriptor (has tpdescrset), that overrides any attribute that might be in the instance; however, the existence of tpdescrget still permits the instance to override its bases (but tpdescrget is called if there is no instance attribute). * We need to invalidate the cache for the updated attribute in all derived classes in the following cases: * an attribute is added or deleted to the class or its base classes * an attribute has its status changed to or from being a data descriptor This file uses Python pseudocode to describe changes necessary to implement CACHEATTR at the C level. Except for class Meta, these are all exact descriptions of the work being done. Except for class Meta the changes go into object.c (Meta goes into typeobject.c). The pseudocode looks somewhat C-like to ease the transformation. ''' NULL = object() def getattr(inst, name): isdata, where = lookup(inst.class, name) if isdata: descr = where[name] if hasattr(descr, "get"): return descr.get(inst) else: return descr value = inst.dict.get(name, NULL) if value != NULL: return value if where == NULL: raise AttributError descr = where[name] if hasattr(descr, "get"): value = descr.get(inst) else: value = descr return value def setattr(inst, name, value): isdata, where = lookup(inst.class, name) if isdata: descr = where[name] descr.set(inst, value) return inst.dict[name] = value def lookup(cls, name): if cls.cache != NULL: pair = cls.cache.get(name) else: pair = NULL if pair: return pair else: for c in cls.mro: where = c.dict if name in where: descr = where[name] isdata = hasattr(descr, "set") pair = isdata, where break else: pair = False, NULL if cls.cache != NULL: cls.cache[name] = pair return pair

''' These changes go into typeobject.c; they are not a complete description of what happens during creation/updates, only the changes necessary to implement CACHEATTRO. ''' from types import ClassType class Meta(type): def invalidate(cls, name): if name in cls.cache: del cls.cache[name] for c in cls.subclasses(): if name not in c.dict: self.invalidate(c, name) def buildcache(cls, bases): for base in bases: if type(base.class) is ClassType: cls.cache = NULL break else: cls.cache = {} def new (cls, bases): self.buildcache(cls, bases) def setbases(cls, bases): self.buildcache(cls, bases) def setattr(cls, name, value): if cls.cache != NULL: old = cls.dict.get(name, NULL) wasdata = old != NULL and hasattr(old, "set") isdata = value != NULL and hasattr(value, "set") if wasdata != isdata or (old == NULL) != (value === NULL): self.invalidate(cls, name) type.setattr(cls, name, value) def delattr(cls, name): self.setattr(cls, name, NULL) -- Aahz (aahz at pythoncraft.com) <*> http://www.pythoncraft.com/ "as long as we like the same operating system, things are cool." --piranha


Python-Dev mailing list Python-Dev at python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/gjcarneiro%40gmail.com

-- Gustavo J. A. M. Carneiro INESC Porto, Telecommunications and Multimedia Unit "The universe is always one step beyond logic." -- Frank Herbert -------------- next part -------------- An HTML attachment was scrubbed... URL: http://mail.python.org/pipermail/python-dev/attachments/20070611/11b461a5/attachment.htm



More information about the Python-Dev mailing list