Steve
Steve

Reputation: 923

Python setattr() can't find attribute that obviously exists

I'm at my wits end. Can't find anything else that helps with this.

dta = {'type': "", 'content': ""}
print dta

>>>{'content': '', 'type': ''}

setattr(dta, 'type', "Steve")

>>>AttributeError: 'dict' object has no attribute 'type'

Upvotes: 4

Views: 11336

Answers (6)

hpaulj
hpaulj

Reputation: 231355

setattr does not work with a dictionary because it does not have an __dict__.

I can make a simple class and instance:

In [88]: class Obj(object):
    ...:     pass
    ...: 
In [89]: ob = Obj()
In [90]: ob.__dict__
Out[90]: {}
In [91]: setattr(ob,'test',34)
In [92]: ob.__dict__
Out[92]: {'test': 34}
In [95]: ob.test
Out[95]: 34

setattr sets an item in the __dict__ of obj, and the resulting attribute can be accessed with .test syntax.

But a dict object does not have a __dict__.

In [96]: dd={1:2, 3:4}
In [97]: dd.__dict__
AttributeError: 'dict' object has no attribute '__dict__'

In [98]: setattr(dd, 'test', 34)
AttributeError: 'dict' object has no attribute 'test'

I add items to the dict with the dictionary indexing notation:

In [99]: dd['test'] = 34
In [100]: dd
Out[100]: {1: 2, 3: 4, 'test': 34}

The dictionary class inherits from object, but in way that does not give each instance a __dict__. Thus setattr has nothing to act on.

If for some reason the []= syntax is awkward, you can use operator.setitem as a function:

In [108]: operator.setitem?
Docstring: setitem(a, b, c) -- Same as a[b] = c.
Type:      builtin_function_or_method
In [109]: operator.setitem(dd, 'test', 23)
In [110]: dd
Out[110]: {1: 2, 3: 4, 'test': 23}

Or use the __setitem__ method

In [111]: dd.__setitem__('test', 1)
In [112]: dd
Out[112]: {1: 2, 3: 4, 'test': 1}

Actually, a dictionary does have attributes. For example its .get method can be fetched with getattr. setattr returns a different error in this example.

In [119]: getattr(dd,'get')
Out[119]: <function dict.get>
In [120]: setattr(dd,'get',dict.get)
...
AttributeError: 'dict' object attribute 'get' is read-only

Without the __dict__ we can't add an attribute to a dictionary. And many, if not all, of the existing attributes are read only. I don't know if a dictionary has an attributes that can be changed.

Actually a dictionary is an object:

In [121]: isinstance(dd,dict)
Out[121]: True
In [122]: isinstance(dd,object)
Out[122]: True

I just imported defaultdict. It too lacks a __dict__. But I was able to use setattr(ddd,'default_factory', int). I think that no attribute is the correct error message when the attribute does not already exist, and it cannot add new ones.

==============

I'd have to double check the documentation on this, but I think setattr(dd,...) delegates to dd.__setattr__(...).

In [168]: dd.__setattr__('test',1)
...
AttributeError: 'dict' object has no attribute 'test'

In [169]: dd.__setattr__('get',None)
...
AttributeError: 'dict' object attribute 'get' is read-only

So the error message is determined by the dict class, not setattr.

Even instances that have a __dict_- might raise errors if setattr is used wrong. For example a number is not a valid attribute name:

In [171]: setattr(ob, 123, 'a')
....
TypeError: attribute name must be string, not 'int'

But setattr can set attributes that can't be accessed with the . syntax:

In [172]: setattr(ob, 'a#re',1)
In [174]: ob.a#re
...
AttributeError: 'Obj' object has no attribute 'a'
In [175]: getattr(ob, 'a#re')
Out[175]: 1

vars() is another way of accessing the __dict__ of an instance:

In [179]: vars(dd)
 ...
TypeError: vars() argument must have __dict__ attribute

Upvotes: 6

wwii
wwii

Reputation: 23743

Dictionaries are subscriptable, the key:value pairs are items instead of attributes.

Items are accessed using a subscript notation:

>>> d = {'a':1, 'b':2}
>>> d['a']
1

Attributes are accessed using dot notation

>>> d.a
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    d.a
AttributeError: 'dict' object has no attribute 'a'

operator.itemgetter is similar to getattr:

>>> import operator
>>> bee = operator.itemgetter('b')
>>> bee(d)
2
>>>  

>>> hasattr(d, '__getitem__')
True
>>> hasattr(d, '__getattr__')
False
>>> 

Upvotes: 1

Steve
Steve

Reputation: 923

docs.python.org/2/library/functions.html#setattr

This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it

OBJECT is not a DICTIONARY. And I probably confusing my JS work (syntax) with the Python code I'm always writing at the same time

So the error message is confusing:

AttributeError: 'dict' object has no attribute 'type'

It should be more like:

'setattr() is for objects not dictionaries'

.

Thanks everyone.

Upvotes: 0

Nikola Dimitroff
Nikola Dimitroff

Reputation: 6237

Python's dictionaries aren't JS objects. When you create a dict, you aren't creating a dynamic object whose properties you can change at runtime as in JS. Instead the dictionary knows how to store pairs of keys and values and does so by overriding the operator [] (def __getitem__(self, key)).

On a more implementation level - calling getattr / setattr is really a shorthand for data.__getattr__("foo") and since dict uses __getitem__ as opposed to __getattr__ the function call fails.

Thus, there's no way to set (or get for that matter) the dict's items using generic attribute functions.

However, you can create your custom dict class that does support that operation (although I wouldn't recommend it):

class AttrDict(dict):
    def __init__(self):
        dict.__init__(self)

    # Override getattr and setattr so that they return the values of getitem / setitem
    def __setattr__(self, name, value):
        self[name] = value

    def __getattr__(self, name):
        return self[name]

data = AttrDict()
data["foo"] = "bar"
print(getattr(data, "foo"))

Upvotes: 9

Ginko
Ginko

Reputation: 395

'setattr()' refers to something else. When you write setattr(dta, 'type', "Steve") you're trying to access the field dta.type, dict class has no attribute type, so it gives an error.

dict_object['key'] is a completely different thing, and it's how dict members should be accessed.

More about settatr() Here

Upvotes: 1

Maurice Meyer
Maurice Meyer

Reputation: 18106

You can assign dict values directly, setattr() is not needed.

dta = {'type': "", 'content': ""}
dta["type"] = "Steve"

Upvotes: 2

Related Questions