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.