4

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'

RzR
  • 3,068
  • 29
  • 26
Steve
  • 905
  • 1
  • 8
  • 32

6 Answers6

9

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"))
Nikola Dimitroff
  • 6,127
  • 2
  • 25
  • 31
  • I completely don't understand you. I'm doing this in the python console. setattr() is a standard Python library function built specifically to change values of dictionalry attributes. – Steve Jan 08 '17 at 15:58
  • 2
    No it isn't. `setattr` is a built-in function specifically to change values of *object attributes*, not dictionary values. – Daniel Roseman Jan 08 '17 at 15:59
  • 1
    Got ya. OBJECT is not a DICT. And I probably confusing my JS work (syntax) with the Python code I'm always writing at the same time – Steve Jan 08 '17 at 16:01
  • @Steve Exactly! See my edit for some further explanations. – Nikola Dimitroff Jan 08 '17 at 16:05
6

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
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • It would be nice if `setattr(obj, attr, val)` first did a test `if (type obj == 'dict') throw error "'obj ' is not an object"`. How handy would that be? – Steve Jan 08 '17 at 17:30
  • if you `s = { "qwe": "asd" }` and `print type s` you get `> > > type dict` so if you in inadvertently pass a dict (i.e. s) to setattr() but it requires an object then setattr() should test for this and throw an error that regards the absence of an object parameter value. Instead what it does do is throw an error that it can't find the attribute in the dict that you requested. – Steve Jan 08 '17 at 18:03
  • Actually it's the dictionary `__setattr__` method that generates your error. – hpaulj Jan 08 '17 at 19:16
  • I don't understand why I can't do setattr(object(), "test", 1), but I can do setattr(Obj(), "test", 1") with your Obj class. The Obj() class is empty, so why can I extend it but not object()? – Samuel Aug 12 '21 at 18:50
  • @Samuel Did you try `object().__dict__`? `Obj` class doesn't add any methods, but it still is a user defined class with a `__dict__`. – hpaulj Aug 12 '21 at 19:11
  • So when you extend object python implicitly adds some methods to it? – Samuel Aug 13 '21 at 22:53
  • @Samuel, Looking at `__dir__`, the main addition is the `__dict__`. That changes the behavior methods like `__setattr__`. There must be better sources for this than my memory. I learned Python is v2 days, and have kept up with the changes as needed. I haven't needed to do anything fancy with classes. – hpaulj Aug 13 '21 at 23:49
  • https://docs.python.org/3/reference/datamodel.html#slots `slots` is a way of bypassing the `__dict__` creation. Lots more details on this page. @Samuel – hpaulj Aug 14 '21 at 01:59
2

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

dta = {'type': "", 'content': ""}
dta["type"] = "Steve"
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • Yeah. I know. But I'm using setattr() successfully else where. Consistency is important. Any thoughts on my setattr() is failing? .... .... while retyping this I realized 'type' is reserved word. Could that be it? – Steve Jan 08 '17 at 15:52
  • a quick test. The error is NOT due to 'type' being a reserved word – Steve Jan 08 '17 at 15:55
  • Setattr, cannot assign a value to a dict-key. Review docs types/classes/setattr ... – Maurice Meyer Jan 08 '17 at 15:56
  • https://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. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123. – Steve Jan 08 '17 at 15:59
  • Got ya. OBJECT is not a DICT – Steve Jan 08 '17 at 16:00
  • 'The function assigns the value to the attribute, provided the object allows it' – Maurice Meyer Jan 08 '17 at 16:01
1

'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

Ginko
  • 385
  • 1
  • 15
1

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
>>> 
wwii
  • 23,232
  • 7
  • 37
  • 77
0

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.

Steve
  • 905
  • 1
  • 8
  • 32