Class attributes, instances and metaclass __getattribute__

Ziga Seilnacht ziga.seilnacht at gmail.com
Tue Aug 8 10:24:54 EDT 2006


Pedro Werneck wrote:
> Hi

[snip]
> Well... I'm not talking about metaclass attributes... that's perfectly
> consistent, agreed.
>
> I'm saying that when the class implements a custom __getattribute__,
> when you try to access the instance attributes from itself, it uses it.
> But if the class is a metaclass, instances of its instances have acess
> to the attribute anyway, but don't use the custom __getattribute__ you
> implemented.

Attribute lookup for instances of a class never calls metaclass'
__getattribute__() method. This method is called only when you
access attributes directly on the class.

[snip]
> And, I'm curious anyway... is it possible to customize attribute access
> in this case in any other way ? What really happens here ?

There are two distinct methods involved in your example; attribute
lookup for classes is controled by metaclass' __getattribute__()
method, while instance attribute lookup is controled by class'
__getattribute__() method.

They are basicaly the same, but they never use ``type(obj).attr`` to
access the class' attributes. The code for these methods would look
something like this in Python:

class Object(object):
    """
    Emulates object's and type's behaviour in attribute lookup.
    """

    def __getattribute__(self, name):
        cls = type(self)

        # you normally access this as self.__dict__
        try:
            dict_descriptor = cls.__dict__['__dict__']
        except KeyError:
            # uses __slots__ without dict
            mydict = {}
        else:
            mydict = dict_descriptor.__get__(self, cls)

        # Can't use cls.name because we would get descriptors
        # (methods and similar) that are provided by class'
        # metaclass and are not meant to be accessible from
        # instances.
        classdicts = [c.__dict__ for c in cls.__mro__]

        # We have to look in class attributes first, since it can
        # be a data descriptor, in which case we have to ignore
        # the value in the instance's dict.
        for d in classdicts:
            if name in d:
                classattr = d[name]
                break
        else:
            # None of the classes provides this attribute; perform
            # the normal lookup in instance's dict.
            try:
                return mydict[name]
            except KeyError:
                # Finally if everything else failed, look for the
                # __getattr__ hook.
                for d in classdicts:
                    if '__getattr__' in d:
                        return d['__getattr__'](self, name)
                msg = "%r object has no attribute %r"
                raise AttributeError(msg % (cls.__name__, name))

        # Check if class' attribute is a descriptor.
        if hasattr(classattr, '__get__'):
            # If it is a non-data descriptor, then the value in
            # instance's dict takes precedence
            if not hasattr(classattr, '__set__') and name in mydict:
                return mydict[name]
            return classattr.__get__(self, cls)

        # Finally, look into instance's dict.
        return mydict.get(name, classattr)

As you can see, it completely avoids calling metaclass'
__getattribute__()
method. If it wouldn't do that, then the metaclass' attributes would
'leak' to instances of its classes. For example, __name__, __mro__
and mro() are some of the descriptors provided by type to every class,
but they are not accesible through instances of these classes,
and shouldn't be, otherwise they could mask some errors in user's code.

Ziga




More information about the Python-list mailing list