Python defaultdict works not as expected [duplicate]
Please explain and maybe help how to fix defaultdict
with a frozen dataclass
key.
Example:
"""Reproduce KeyError: defaultdict with dataclass keys"""
import collections
import dataclasses
import unittest
@dataclasses.dataclass(frozen=True)
class ReservationCoverageKey:
"""AWS RDS Reservation dimensions"""
database_engine: str
instance_family: str
region: str
class TestCase(unittest.TestCase):
"""Main test cases"""
def test_dict_with_dataclass_keys(self):
"""Test normal dict with dataclass key"""
result = {}
key = ReservationCoverageKey(
database_engine="PostgreSQL",
instance_family="db.t3",
region="eu-west-1",
)
result[key] = 0
result[key] += 1
result[key] += 2
self.assertEqual(result[key], 3)
def test_defaultdict_with_dataclass_keys(self):
"""Test defaultdict with dataclass key"""
result = collections.defaultdict(default_factory=int)
key = ReservationCoverageKey(
database_engine="PostgreSQL",
instance_family="db.t3",
region="eu-west-1",
)
result[key] += 1
result[key] += 2
self.assertEqual(result[key], 3)
if __name__ == "__main__":
unittest.main()
Reproduce:
$ python3 defaultdict_keyerror_reproduce.py
E.
======================================================================
ERROR: test_defaultdict_with_dataclass_keys (__main__.TestCase)
Test defaultdict with dataclass key
----------------------------------------------------------------------
Traceback (most recent call last):
File "/var/tmp/sscce/defaultdict_keyerror_reproduce.py", line 41, in test_defaultdict_with_dataclass_keys
result[key] += 1
KeyError: ReservationCoverageKey(database_engine='PostgreSQL', instance_family='db.t3', region='eu-west-1')
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)
The testcase with dict
works, but not with the defaultdict
.
collections.defaultdict(default_factory=int)
creates a default dict with no default value type, and one value (the type int
at the key "default_factory"
) because default_factory
can only be passed as positional argument. Any keyword arguments are considered key-value pairs for the dictionary:
defaultdict(None, {'default_factory': int})
You need to do collections.defaultdict(int)
, which produces defaultdict(int, {})