Amador Pahim

Python

Class Attribute Resolution

apahim

Have you ever heard about “data descriptors”? No? One more reason to stick around.

Some time ago I was reading the official Python documentation and it was quite hard for me to figure out the full order of the class attribute resolution from there. That’s when I decided to write it down here, hopefully in a more clear way. Let’s get right into it.

Consider a class with one class variable:

class MyClass:
    a = 1

That variable will be available on the class instances, right?

>>> m = MyClass()
>>> m.a
1

Now what happens if I decide to have an instance attribute with the same name, like this?

class MyClass:
    a = 1

    def __init__(self):
        self.a = 2

Well, the instance attribute will have precedence over the class variable:

>>> m = MyClass()
>>> m.a
2

If I ever want to use the class variable, I can still do it using the class, without creating an instance:

>>> MyClass.a
1

I could also access it from the instance, but in a trickier way:

>>> m = MyClass()
>>> m.a
2
>>> m.__class__.a
1

So, at this point, we have the following order of precedence in the class attribute resolution:

  1. Instance attributes.
  2. Class variables.

So far so good? Now what happens if I try to use a non-existing attribute? An exception, right?

>>> print(m.b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'b'

Python offers you a way to handle missing attributes. That’s the __getattr__ magic method:

class MyClass:
    a = 1
    
    def __init__(self):
        self.a = 2

    def __getattr__(self, item):
        return 3

Now any missing attribute call will return 3:

>>> m = MyClass()
>>> m.a
2
>>> m.b
3
>>> m.c
3
>>> m.d
3

That adds to our attribute resolution order:

  1. Instance attributes.
  2. Class variables.
  3. __getattr__

Deep in the documentation, we have:

Data descriptors with __set__() and __get__() defined always override a redefinition in an instance dictionary.

Got it? It means that, if we have a class variable which is a data descriptor, that class variable will have precedence over an instance attribute (instance dictionary is where the attributes are described). But, what is a data descriptor? That quote already gives us a hint, but in the same paragraph, we read:

A descriptor can define any combination of __get__()__set__() and __delete__(). If it does not define __get__(), then accessing the attribute will return the descriptor object itself unless there is a value in the object’s instance dictionary. If the descriptor defines __set__() and/or __delete__(), it is a data descriptor; if it defines neither, it is a non-data descriptor. Normally, data descriptors define both __get__() and __set__(), while non-data descriptors have just the __get__() method.

So, the Python protocol says that a data descriptor is an object that implements the methods __set__ and __get__. Shall we play with that? Since we got this far…

class DataDescriptor:
    def __set__(self, instance, value):
        pass
    
    def __get__(self, instance, owner):
        return 1


class MyClass:    
    a = DataDescriptor()

    def __init__(self):
        self.a = 2

    def __getattr__(self, item):
        return 3

Now, printing the attributes:

>>> m = MyClass()
>>> m.a
1
>>> m.b
3

Ok, now we’ve found a way to have a class variable with higher precedence than the instance attributes, as long as it implements the data descriptor protocol. One more item to our list:

  1. Class data escriptors.
  2. Instance attributes.
  3. Class simple variables.
  4. __getattr__

Last but not least, we have the __getattribute__, a magic method with higher precedence than everything else:

class Descriptor:
    def __set__(self, instance, value):
        pass

    def __get__(self, instance, owner):
        return 1


class MyClass:
    a = Descriptor()

    def __init__(self):
        self.a = 2

    def __getattr__(self, item):
        return 3

    def __getattribute__(self, item):
        return 4
>>> m = MyClass()
>>> m.a
4
>>> m.b
4
>>> m.c
4
>>> m.d
4

That gives us the final class attribute resolution order:

  1. __getattribute__
  2. Class data descriptors.
  3. Instance attributes.
  4. Class simple variables.
  5. __getattr__

Now, tell me: did you learn something new?

:wq!

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top