Search code examples
pythondefaultdict

Python defaultdict object's attribute raises AttributeError


I have Python 2.7 code that uses defaultdict to create a dictionary mydict that creates default objects of MyClass, as shown below.

My understanding is that if the key foo does not exist in the defaultdict, then a new object of MyClass would be created, and since I have an __init__() method, then the attribute value would also exist. So why doesn't the code below work, where I have:

print mydict['foo'].value # Raises AttributeError

However, this code does work:

mydict['foo'].value = 1 # Works fine

What's the correct approach to solve the error? (I'm a C++/Java programmer, by the way.)

Below is the entirety of the code:

from collections import defaultdict

class MyClass(object):
    def __init__(self):
        self.value = 0

mydict = defaultdict(lambda: MyClass)
print mydict['foo'].value # Raises AttributeError
#mydict['foo'].value += 1 # Raises AttributeError
#mydict['foo'].value = 1 # Works fine

The result:

AttributeError                            Traceback (most recent call last)
<ipython-input-16-c9dd776d73de> in <module>()
      6 
      7 mydict = defaultdict(lambda: MyClass)
----> 8 print mydict['foo'].value # Raises AttributeError
      9 #mydict['foo'].value += 1 # Raises AttributeError
     10 #mydict['foo'].value = 1 # Works fine

AttributeError: type object 'MyClass' has no attribute 'value'

Solution

  • When you specify defaultdict(lambda: MyClass), what the default dict produces is the MyClass definition (through the lambda) which does not have value as it is not defined in the class, as that is what is being returned with that particular construct (default dict will invoke the first argument that it was constructed with if it was a callable object, and you can see this yourself already in the exception message you have included in your question). See this example:

    >>> MyClass.value
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'MyClass' has no attribute 'value'
    >>> mydict['foo'].value = 1
    >>> MyClass.value
    1
    

    If you truly want to return the raw class, provide that value attribute like so (but it will be shared across all default values)

    class MyClass(object):
        value = None
    

    Or do what you really want, which is to provide a default instance of the class, which you can call that manually in the lambda, or reduce it to simply:

    mydict = defaultdict(MyClass)