Python's hasattr on list values of dictionaries always returns false?
Asked Answered
H

6

41

I have a dictionary that sometimes receives calls for non-existent keys, so I try and use hasattr and getattr to handle these cases:

key_string = 'foo'
print "current info:", info
print hasattr(info, key_string)
print getattr(info, key_string, [])
if hasattr(info, key_string):
    array = getattr(info, key_string, [])
array.append(integer)
info[key_string] = array
print "current info:", info

The first time this runs with integer = 1:

current info: {}
False
[]
current info: {'foo': [1]}

Running this code again with integer = 2:

instance.add_to_info("foo", 2)

current info: {'foo': [1]}
False
[]
current info: {'foo': [2]}

The first run is clearly successful ({'foo': [1]}), but hasattr returns false and getattr uses the default blank array the second time around, losing the value of 1 in the process! Why is this?

Historiography answered 23/5, 2012 at 17:13 Comment(0)
P
68

hasattr does not test for members of a dictionary. Use the in operator instead, or the .has_key method:

>>> example = dict(foo='bar')
>>> 'foo' in example
True
>>> example.has_key('foo')
True
>>> 'baz' in example
False

But note that dict.has_key() has been deprecated, is recommended against by the PEP 8 style guide and has been removed altogether in Python 3.

Incidentally, you'll run into problems by using a mutable class variable:

>>> class example(object):
...     foo = dict()
...
>>> A = example()
>>> B = example()
>>> A.foo['bar'] = 'baz'
>>> B.foo
{'bar': 'baz'}

Initialize it in your __init__ instead:

class State(object):
    info = None

    def __init__(self):
        self.info = {}
Parotitis answered 23/5, 2012 at 17:19 Comment(2)
I'll be damned! I thought I was going crazy. I've always used the if/in/else structure for these dictionary testing situations with default values. This time since the actual situation is actually much more intricate than my example, with a few other hasattrs flying around, I favored that "syntax" instead... which obviously doesn't work! I'll remember this gotcha from now on, thanks!Historiography
Also, I'm dealing with info in init appropriately, just wanted to streamline the example.Historiography
H
16

A dictionary key is not the same as an object attribute

thing1 = {'a', 123}
hasattr(thing1, 'a') # False
class c: pass
thing2 = c()
thing2.a = 123
hasattr(thing2, 'a') # True
Hydrous answered 23/5, 2012 at 17:18 Comment(1)
Ah. This was driving me mad. Thanks for sharing example.Funereal
B
2

To test for elements in a list/dictionary, use in. To use defaults, you can use dict.get:

def add_to_info(self, key_string, integer):
    array = self.info.get(key_string, [])
    array.append(integer)
    self.info[key_string] = array

Or use defaultdict:

from collections import defaultdict
class State(object):
    info = defaultdict(list)

    def add_to_info(self, key_string, integer):
        self.info[key_string].append(integer)
Brana answered 23/5, 2012 at 17:21 Comment(0)
H
2

If you convert your dictionary to a dot dictionary you can use hasattr and getattr for it:

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

newdict = dotdict(olddict)
Hypoploid answered 26/6, 2022 at 13:15 Comment(0)
S
1

Looks like all you need is one line:

def add_to_info(self, key_string, integer):
    self.info.setdefault(key_string, []).append(integer)
Syphon answered 23/5, 2012 at 17:24 Comment(0)
M
-1

You may use .get() method on a dict type object. This method doesnt raise a key error if isnt defined. Also, as the getattr() for objects you may specify on it a default value.

>> {'name': 'Me'}.get('name1', 'StackOverflow')
>> 'StackOverflow'
Manns answered 1/4, 2020 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.