Can't Set Attributes on Instance of "Object" Class

Can't set attributes on instance of object class

To support arbitrary attribute assignment, an object needs a __dict__: a dict associated with the object, where arbitrary attributes can be stored. Otherwise, there's nowhere to put new attributes.

An instance of object does not carry around a __dict__ -- if it did, before the horrible circular dependence problem (since dict, like most everything else, inherits from object;-), this would saddle every object in Python with a dict, which would mean an overhead of many bytes per object that currently doesn't have or need a dict (essentially, all objects that don't have arbitrarily assignable attributes don't have or need a dict).

For example, using the excellent pympler project (you can get it via svn from here), we can do some measurements...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

You wouldn't want every int to take up 144 bytes instead of just 16, right?-)

Now, when you make a class (inheriting from whatever), things change...:

>>> class dint(int): pass
...
>>> asizeof.asizeof(dint(23))
184

...the __dict__ is now added (plus, a little more overhead) -- so a dint instance can have arbitrary attributes, but you pay quite a space cost for that flexibility.

So what if you wanted ints with just one extra attribute foobar...? It's a rare need, but Python does offer a special mechanism for the purpose...

>>> class fint(int):
... __slots__ = 'foobar',
... def __init__(self, x): self.foobar=x+100
...
>>> asizeof.asizeof(fint(23))
80

...not quite as tiny as an int, mind you! (or even the two ints, one the self and one the self.foobar -- the second one can be reassigned), but surely much better than a dint.

When the class has the __slots__ special attribute (a sequence of strings), then the class statement (more precisely, the default metaclass, type) does not equip every instance of that class with a __dict__ (and therefore the ability to have arbitrary attributes), just a finite, rigid set of "slots" (basically places which can each hold one reference to some object) with the given names.

In exchange for the lost flexibility, you gain a lot of bytes per instance (probably meaningful only if you have zillions of instances gallivanting around, but, there are use cases for that).

Why can't you add attributes to object in python?

Notice that an object instance has no __dict__ attribute:

>>> dir(object())
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__']

An example to illustrate this behavior in a derived class:

>>> class Foo(object):
... __slots__ = {}
...
>>> f = Foo()
>>> f.bar = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'

Quoting from the docs on slots:

[...] The __slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because __dict__ is not created for each instance.

EDIT: To answer ThomasH from the comments, OP's test class is an "old-style" class. Try:

>>> class test: pass
...
>>> getattr(test(), '__dict__')
{}
>>> getattr(object(), '__dict__')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute '__dict__'

and you'll notice there is a __dict__ instance. The object class may not have a __slots__ defined, but the result is the same: lack of a __dict__, which is what prevents dynamic assignment of an attribute. I've reorganized my answer to make this clearer (move the second paragraph to the top).

Can't set an arbitrary attribute in an instance of an object

I don't think there's a better way than subclassing Surface if you really need to be able to assign arbitrary attributes to an instance. The subclass can be empty if you don't need to add any other features (the instance __dict__ will be created automatically).

class MySurface(pygame.Surface):
pass

However, I'm not sure you really need that ability.

A better design is generally to encapsulate the Surface object and whatever other data you have as attributes of some other object type. In Pygame you'll often use a subclass of pygame.sprite.Sprite to bundle a Surface (as the image attribute) with coordinates (as part of the rect attribute). If you have any other data you need to bundle, the Sprite can take it too. Sprite instances have a __dict__ by default, even if you don't write your own subclass.

AttributeError: can't set attribute in python

items[node.ind] = items[node.ind]._replace(v=node.v)

(Note: Don't be discouraged to use this solution because of the leading underscore in the function _replace. Specifically for namedtuple some functions have leading underscore which is not for indicating they are meant to be "private")

What allows bare class instances to have assignable attributes?

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.

Attribute assignment to built-in object

Python stores attributes in a dict. You can add attributes to MyClass, see it has a __dict__:

>>> class MyClass(object):
>>> pass
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

The important difference is that object has no __dict__ attribute.

>>> dir(object)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

More detailed explanations:

  • Can't set attributes of object class
  • Why can't you add attributes to object in python?

AttributeError: can't set attribute - Python instance attribute assignment

If you look at the source code (or even if you just print(scrapy.Spider.logger) you can see that Spider.logger is a property, and in particular without a setter defined, so you can't easily assign to it.

You don't necessarily need to create your own logger if you want to add additional handlers though, so I'm not sure what you're trying to achieve beyond that. Though if you ''really'' wanted to override the default self.logger, since you're subclassing Spider there's nothing stopping you from adding something like:

@property
def logger(self):
return logging.getLogger('my_logger')

to your class.



Related Topics



Leave a reply



Submit