Search code examples
pythonclasspropertiesinstance

Why does a calculated property return a property object when called from the class object, but an instance of the class returns the expected object?


class a_class():
    _x = []

    @property
    def x(self):
        return a_class._x

ac1 = a_class()

print(a_class.x)

print(ac1.x)

Why does the first print return a property object, while the second print returns the list object?


Solution

  • Accessing the property on the class won't call it; it's only called on instances. If it were to be called on classes, it would break method lookup, because method lookup reads attributes from the class of an instance, not from the instance itself.

    class cls:
        @property
        def attr(self):
            print('Property was called')
    
    cls.attr # doesn't output anything, and evaluates to the property
    cls().attr # calls the property, outputting the string
    

    When an attribute is looked up on an object (class instance) obj, Python will do the following:

    1. Call type(obj).__getattribute__(obj,attr)/type(obj).__getattr__(obj,attr) where attr is the string name of the attribute (assuming the attribute exists, we only need to worry about the latter).
    2. If the attribute is a descriptor (an object whose type defines a __get__ method), then those methods will call the type(descriptor).__get__(descriptor), and return that value as the attribute's value.
    3. Otherwise, return the attribute value.

    Objects of type property define the __get__ method, and are therefore descriptors. For properties specifically, they will call their wrapped function. Therefore, when they are looked up on an instance, that __get__ method will call the wrapped function. Equally, it won't get called when it's looked up on the class.

    I hope this helps!