Python - Using defaultdict to make dictionary of custom objects
Asked Answered
M

3

8

I have created the following class. Package, website and comments are all strings and distroDict is a (string, list) dictionary.

class TableEntry(object):

    def __init__(self, package, website, distroDict, comments):
        self.package = package
        self.website = website
        self.distroDict = distroDict
        self.comments = comments

I want to use defaultdict(TableEntry) to make a (string, TableEntry) custom dictionary.

tableDict = defaultdict(TableEntry)
entry = TableEntry(package, website, distroDict, comments)
tableDict[package].append(entry)

I want the package to be the key and the entry object to be the value. I can use values within the entry object but if I try to append it to tableDict I receive the following error.

Traceback (most recent call last):
  File "wiki.py", line 151, in <module>
    printMetaData(lines, f)
  File "wiki.py", line 73, in printMetaData
    tableDict[package].append(entry)
TypeError: __init__() takes exactly 5 arguments (1 given)

I have also tried the following:

tableDict[package].append(TableEntry(package, website, distroDict, comments))

And receive the same error essentially:

Traceback (most recent call last):
  File "wiki.py", line 150, in <module>
    printMetaData(lines, f)
  File "wiki.py", line 73, in printMetaData
    tableDict[package].append(TableEntry(package, website, distroDict, comments))
TypeError: __init__() takes exactly 5 arguments (1 given)
Malvia answered 17/12, 2015 at 3:24 Comment(3)
Isn't this a list of table entries, so don't you want of defaultdict(list) that you are appending TableEntrys to? Default dict expects a zero parameter function for creating it's default value, so you can't actually do defaultdict(TableEntry).Duhamel
@Duhamel wouldnt defaultdict(list) give me a dict with { string, [list] }? Whereas I am looking for a dict with { string, TableEntry }. I need to map a String to an object than can hold multiple pieces of information and a string, list dict will not suffice. If I cannot do defaultdict(TableEntry) how else could I accomplish this?Malvia
defaultdist(list) will give you a a { string: [] } dictionary but as you are appending a TableEntrys you needed a collection, e.g. a dictionary of { string: [TableEntry, TableEntry, ...]}. However, if you just want a dictionary of {string: TableEntry} why do you need a defaultdict, wouldn't tableDict = {}; tableDict[package] = TableEntry(...) not work?Duhamel
D
8

When you try to get something from a defaultdict it returns an instance of the default_factory you instantiated it with, which in your case is TableEntry that has no method append. But that's actually not why you are getting the error. This happens because when an instance is returned it is also evaluated, and since TableEntry doesn't have the arguments it needs, the Python interpreter will complain. But even if TableEntry was given arguments in the defaultdict call, you would probably get a TypeError because defaultdict needs to be called with a callable (and not an instance of a class, as it would be).

In summary, you cant instantiate a defaultdict with an non-callable unless you use subclassing. Instead you should do something like this:

class TableEntry(object):
    def add(self, package, website, distroDict, comments):
        self.package = package
        self.website = website
        self.distroDict = distroDict
        self.comments = comments

tableDict = defaultdict(TableEntry)
entry = ("package", "website", "distroDict", "comments")
tableDict["package"].add(*entry)

Then you can do e.g. tableDict["package"]["website"] to get the string you wrote to self.website.

Doughty answered 30/3, 2016 at 21:29 Comment(1)
I will say, odd solution to be accepted as it makes no sense, perhaps the blame is on the question not making much sense either. Surely the point of .add/.append is to actually add or append to a collection? In this example there is no collection, you are just setting the values in a roundabout way!Gratianna
O
1

Another approach is to make the arguments optional for the __init__ method, and assign the entry object to tableDict directly.

class TableEntry:
    def __init__(self, package=None, website=None, distroDict=None, comments=None):
        self.package = package
        self.website = website
        self.distroDict = distroDict
        self.comments = comments

tableDict = defaultdict(TableEntry)
tableDict["package"] = TableEntry("package", "website", "distroDict", "comments")
Oaken answered 24/10, 2021 at 0:25 Comment(1)
I don't think you need defaultdict for this to work, just replace defaultdict(TableEntry) with {}Gratianna
G
0

To use defaultdict you need to provide actual default values in the class being default-initialised. Otherwise you will get a runtime error when you try to access it. If you immediately manually initialise the value then you didn't need a defaultdict.

To fulfil the question's tableDict[package].append(entry) all you need is defaultdict(list):

tableDict = defaultdict(list)
entry = TableEntry(package, website, distroDict, comments)
tableDict[package].append(entry)

But here are some examples of actually using defaultdicts to spare yourself some initialisation checks and code with dataclasses:

from dataclasses import dataclass, field
from collections import defaultdict
from typing import Any, Dict, List

@dataclass
class TableEntry:
     package: str = ""
     website: str = ""
     distroDict: Dict[str, Any] = field(default_factory=defaultdict)
     comments: List[str] = field(default_factory=list)

tableDict = defaultdict(TableEntry)
tableDict["mytestexample"]
>>> TableEntry(package='', website='', distroDict='', comments=[])

Here is perhaps a more complete example of defaultdict usability, with some names changed to relate more to a package dependency list:

from dataclasses import dataclass, field
from collections import defaultdict
from typing import Any, Dict, List

@dataclass
class DependencyEntry:
   package: str
   website: str
   distroDict: Dict[str, Any] = field(default_factory=defaultdict)
   comments: List[str] = field(default_factory=list)

@dataclass
class DependencyGroup:
   dependency_count: int = 0
   dependencies: List[DependencyEntry] = field(default_factory=list)

dependencyTable = defaultdict(DependencyGroup)
dependencyTable["mytestpackage"]
>>> DependencyGroup(dependency_count=0, dependencies=[])

dependencyTable["mytestpackage"].dependencies
>>> []

# add a new instance directly into the dependency list
dependencyTable["mytestpackage"].dependencies.append(
    DependencyEntry("Test", "example.com", {"distroArch": "metadata"}, ["this is a comment"]))

dependencyTable["mytestpackage"].dependencies
>>> [DependencyEntry(package='Test', website='example.com', distroDict={'distroArch': 'metadata'}, comments=['this is a comment'])]
Gratianna answered 15/4 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.