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
:EDIT: To answer ThomasH from the comments, OP's test class is an "old-style" class. Try:[...] 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.
>>> 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 int
s 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 int
s, 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.
__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:
Another interesting article about Python's data model includingSome 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.
__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
Append Dataframe to Excel with Pandas
Using Python Requests: Sessions, Cookies, and Post
How to Get a List of Keywords in Python
List Sorting with Multiple Attributes and Mixed Order
Check If an Item Is in a Nested List
"Overflowerror: Python Int Too Large to Convert to C Long" on Windows But Not MAC
Vectorized Numpy Linspace for Multiple Start and Stop Values
Using Configparser to Read a File Without Section Name
Merging Dictionary Value Lists in Python
In Tensorflow, Get the Names of All the Tensors in a Graph
Matplotlib: Finding Out Xlim and Ylim After Zoom
Return List of Items in List Greater Than Some Value
Fastest Way to Sort Each Row in a Pandas Dataframe
How to Resolve a Tesseractnotfounderror
How to Log a Python Error with Debug Information
How to Add Trendline in Python Matplotlib Dot (Scatter) Graphs
Regex for Existence of Some Words Whose Order Doesn't Matter