Why Can't You Add Attributes to Object in Python

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).

Why can't I directly add attributes to any python object?

My guess, is that the implementation of datetime uses __slots__ for better performance.

When using __slots__, the interpreter reserves storage for just the attributes listed, nothing else. This gives better performance and uses less storage, but it also means you can't add new attributes at will.

Read more here: http://docs.python.org/reference/datamodel.html

How can I create an object and add attributes to it?

You could use my ancient Bunch recipe, but if you don't want to make a "bunch class", a very simple one already exists in Python -- all functions can have arbitrary attributes (including lambda functions). So, the following works:

obj = someobject
obj.a = lambda: None
setattr(obj.a, 'somefield', 'somevalue')

Whether the loss of clarity compared to the venerable Bunch recipe is OK, is a style decision I will of course leave up to you.

If everything is an object, why can't I add attributes to some?

Yes, everything is an object. However, everything being an object does not mean that everything takes arbitrary attributes.

Integers in Python are objects, and have attributes and methods (which are just callable attributes):

>>> x = 6
>>> x.real
6
>>> x.imag
0
>>> x.bit_length()
3

To support arbitrary attributes, an object needs to have a __dict__ mapping. Integers don't have such a mapping:

>>> x.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__dict__'

Other objects do, like functions, for example:

>>> def foo(): pass
...
>>> foo.__dict__
{}
>>> foo.bar = 'baz'

But a __dict__ mapping comes with a price: a larger memory footprint for such objects. Since Python uses a lot of integers, it makes sense to not give them a __dict__ mapping, to save memory. You very rarely would need to give them extra attributes anyway.

You can define your own classes that produce instances without a __dict__ attribute, by giving your class a __slots__ class variable; this defines the fixed attributes an instance supports. This lets you benefit from the same memory savings:

>>> class Demo(object):
... __slots__ = ('foo',)
...
>>> d = Demo()
>>> d.foo = 'bar'
>>> d.bar = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Demo' object has no attribute 'bar'

And vice-versa, if you create a subclass of int and not give your subclass a __slots__ variable, you can add arbitrary attributes to that subclass:

>>> class MyInt(int):
... pass
...
>>> mi = MyInt(6)
>>> mi.foo = 'bar'

How can we add attribute to the Person object in the project ? Python

Do you mean something like this?

New person.py file for experimental purposes:

class Contact:
c: str = None # contact has one attribute called c

class Person:
def __init__(self,
c=None,
driver=None,
scrape=True,
close_on_complete=True):
if c == None:
self.c = [] # use empty list
else:
self.c = c # take list from parameters

def add_c(self, c):
self.c.append(c) # append single contact to list

c1 = Contact()
c1.c = "Joe"
c2 = Contact()
c2.c = "John"
contact_list = [c1, c2]
p = Person(contact_list)
print(p.c[0].c) # print first contact
print(p.c[1].c) # print second contact
print("Again with other initialization:")
q = Person()
q.add_c(c1)
q.add_c(c2)
print(q.c[0].c) # print first contact
print(q.c[1].c) # print second contact
$ python3 person.py
Joe
John
Again with other initialization:
Joe
John
$

The error 'Person' object has no attribute 'c' may indicate that you have an indentation problem.

Nevertheless, please try to provide a mre (https://stackoverflow.com/help/minimal-reproducible-example). Else, we cannot reproduce your error and have to guess.

New person.py from github code (reduced, class Contact added according to its usage):

class Contact:
c: str = None # contact has one attribute called c
def __init__(self, name):
self.c = name

class Person():

def __init__(
self,
c=None,
driver=None,
scrape=True,
close_on_complete=True,
):
self.c = c or []

def add_c(self, c):
self.c.append(c)

# name = "John Smith"
# c = Contact(name) # above was the append do not overwrite here

# self.add_c(c) # we do want to call ourselves

c1 = Contact("Joe Black")
p=Person()
p.add_c(c1)
print(p.c[0].c)

And running:

$ python3 person.py
Joe Black
$

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).

Adding attributes to python objects

You can add attributes to any object that has a __dict__.

  • x = object() doesn't have it, for example.
  • Strings and other simple builtin objects also don't have it.
  • Classes using __slots__ also do not have it.
  • Classes defined with class have it unless the previous statement applies.

If an object is using __slots__ / doesn't have a __dict__, it's usually to save space. For example, in a str it would be overkill to have a dict - imagine the amount of bloat for a very short string.

If you want to test if a given object has a __dict__, you can use hasattr(obj, '__dict__').

This might also be interesting to read:

Some objects, such as built-in types and their instances (lists, tuples, etc.) do not have a __dict__. Consequently user-defined attributes cannot be set on them.

Another interesting article about Python's data model including __dict__, __slots__, etc. is this from the python reference.

Why is adding attributes to an already instantiated object allowed?

A leading principle is that there is no such thing as a declaration. That is, you never declare "this class has a method foo" or "instances of this class have an attribute bar", let alone making a statement about the types of objects to be stored there. You simply define a method, attribute, class, etc. and it's added. As JBernardo points out, any __init__ method does the very same thing. It wouldn't make a lot of sense to arbitrarily restrict creation of new attributes to methods with the name __init__. And it's sometimes useful to store a function as __init__ which don't actually have that name (e.g. decorators), and such a restriction would break that.

Now, this isn't universally true. Builtin types omit this capability as an optimization. Via __slots__, you can also prevent this on user-defined classes. But this is merely a space optimization (no need for a dictionary for every object), not a correctness thing.

If you want a safety net, well, too bad. Python does not offer one, and you cannot reasonably add one, and most importantly, it would be shunned by Python programmers who embrace the language (read: almost all of those you want to work with). Testing and discipline, still go a long way to ensuring correctness. Don't use the liberty to make up attributes outside of __init__ if it can be avoided, and do automated testing. I very rarely have an AttributeError or a logical error due to trickery like this, and of those that happen, almost all are caught by tests.



Related Topics



Leave a reply



Submit