Home > Net >  What allows bare class instances to have assignable attributes?
What allows bare class instances to have assignable attributes?

Time:12-18

I am trying to fill in a gap in my understanding of how Python objects and classes work.


A bare object instance does not support attribute assignment in any way:

> object().a = 5
# or
> setattr(object(), 'a', 5)

AttributeError: 'object' object has no attribute 'a'

I assume that this is because a bare object instance does not possess an __dict__ attribute:

> '__dict__' in dir(object())

False

However a bare object instance does have a defined __setattr__ attribute, so this is a bit confusing to me:

> '__setattr__' in dir(object())

True

An instance of a regular empty class on the other hand has full ability of attribute assignment:

class B(object):
    pass

> B().a = 5
> setattr(B(), 'a', 5)

My question is: what inherent difference between an object instance and a class B(object) instance allows the former to have assignable attributes, if B inherits directly from object?

CodePudding user response:

The object() class is like a fundamental particle of the python universe, and is the base class (or building block) for all objects (read everything) in Python. As such, the stated behavior is logical, for not all objects can (or should) have arbitrary attributes set. For example, it wouldn't make sense if a NoneType object could have attributes set, and, just like object(), a None object also does not have a __dict__ attribute. In fact, the only difference in the two is that a None object has a __bool__ attribute. For example:

n = None
o = object()

type(n)
>>> <class 'NoneType'>

set(dir(n)) - set(dir(o))
>>> {'__bool__'}

isinstance(n, object)
>>> True

bool(n)
>>> False

Inheritance from object is automatic, and, just like any other means of inheriting, one can add their own class methods and attributes to the child. Python automatically adds the __dict__ attribute for custom data types as you already showed.

In short, it is much easier to add an object's __dict__ attribute than to take it away for objects that do not have custom writable attributes (i.e. the NoneType).

Update based on comment:

Original comment:

Would it then be safe to assume that it is __setattr__ that checks for the existence of __dict__ and raises an exception accordingly? – bool3max

In CPython, the logic behind object.__setattr__(self, name, value) is implemented by Objects/object.c _PyObject_GenericSetAttrWithDict (see CPython source code). Specifically, it looks like if the name argument is a string, then the object is checked for a __dict__ object in one of its slots and makes sure it is "ready" (see this line).

The readiness state of the object is determined by the PyType_Ready(), briefly described and quoted from here:

Defining a Python type in C involves populating the fields of a PyTypeObject struct with the values you care about. We call each of those fields a “slot”.

On[c]e the definition is ready, we pass it into the PyType_Ready() function, which does a number of things, inclulding exposing most of the type definition to Python’s attribute lookup mechanism.

  • Related