Search code examples
python-3.xdictionaryclasstypeerror

How to create extended dict Class requiring key/values on get()


I'm trying to build a custom class that will require keys and/or values when getting them from a dict. This is a simple version of what I have written, getRequired and is only checking for keys.

class ExtendedDict(dict):
    def getRequired(self, key, default=None):
        """
        Custom get method that raises an error if the key is not found.
        """
        if key in self:
            return self[key]
        elif default is not None:
            return default
        else:
            raise KeyError(f"Key '{key}' not found in the dictionary")

# Create a nested dictionary
nested_dict = {
    'person1': {'name': 'Alice', 'age': 30},
    'person2': {'name': 'Bob', 'age': 25},
    'person3': {'name': 'Charlie', 'age': 40}
}

# Create an instance of ExtendedDict
extended_nested_dict = ExtendedDict(nested_dict)

# Access 'age' for 'person1' using getRequired
try:
    alice_age = extended_nested_dict.getRequired['person1'].getRequired('age')
    print(f"Alice's age: {alice_age}")
except KeyError as e:
    print(e)

When attempting this code, an error is thrown.TypeError: 'method' object is not subscriptable When attempting the same code, to access 'person1' rather than 'age' the value is returned. My understanding is that the error is given because the nested dictionary is not using ExtendedDict but is using the standard dict.

I'm looking for feedback on this, or someone to correct my understanding. Thanks for the help.


Solution

  • Well, you have two problems. The first is a small syntax problem:

    extended_nested_dict.getRequired['person1']
    # TypeError: 'method' object is not subscriptable
    

    Here, you are trying to subscript a method. What you probably meant to do was call get required (which uses parentheses, not square brackets):

    extended_nested_dict.getRequired('person1')  # OK
    

    The other issue, which I believe is the main question you really have is that you are only wrapping the outer-most dictionary, but you also want nested dictionaries to become of type ExtendedDict. To do this, you can implement it in the __init__ method. Something like this:

    from collections import UserDict
    
    # use UserDict to avoid subclassing issues with the builtin type
    
    class ExtendedDict(UserDict):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for k, v in self.items():
                if isinstance(v, dict):
                    extended_value = ExtendedDict(v)
                    self[k] = extended_value
        def getRequired(self, key, default=None):
            ... # same as you had it
    

    Then you can see the nested dictionaries (no matter how deeply nested) will also become an ExtendedDict:

    # Create a nested dictionary
    nested_dict = {
        'person1': {'name': 'Alice', 'age': 30},
        'person2': {'name': 'Bob', 'age': 25},
        'person3': {'name': 'Charlie', 'age': 40}
    }
    
    # Create an instance of ExtendedDict
    extended_nested_dict = ExtendedDict(nested_dict)
    
    person = extended_nested_dict.getRequired('person1')
    age = person.getRequired('age')
    name = person.getRequired('name')
    print(f'{name} is {age} years old')
    
    assert type(person) is ExtendedDict  # True
    

    Some additional tweaking would be needed if you also want this to work within other nested collections, such as lists, tuples, etc.