computer science

Python Code for Dictionary Dot Accessible & Nested Key Handling

CoreTree 2023. 6. 24. 14:30
반응형

In Python, dictionaries are a versatile data structure that allows us to store key-value pairs. However, they might not always be convenient to use, especially when we're dealing with nested structures and we'd prefer to access them using dot notation, much like we would with an object's attributes. So, today, we're going to create a Python class that allows for dot-accessible dictionary keys and automatic nested key handling.

 

반응형

 

Class Creation

The core of our solution is to create a Python class that inherits from the dict class, and override certain magic methods to change the behavior of our dictionary. We'll call our class DotAccessibleDict.

 

dot_dict.py

import json

class DotAccessibleDict(dict):
    """
    A dictionary that allows dot notation to access keys.
    also it allows to access and generate nested structure.
      ex) data = DotAccessibleDict()
          data.new_key.generate_test.nested_key = 1

    functions:
      get("key", default) : returns the value for key if key is in the dictionary, else default
      dict() : returns a dict object
      json() : returns a json string
    """
    def __init__(self, *args, **kwargs):
        super(DotAccessibleDict, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
        if kwargs:
            for k, v in kwargs.items():
                self[k] = v

    def __getattr__(self, attr):
        if attr in self:
            return self[attr]
        else:
            self[attr] = type(self)()
            return self[attr]

    def __setattr__(self, key, value):
        if isinstance(value, dict):
            self[key] = type(self)(value)
        else:
            self[key] = value

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            super(DotAccessibleDict, self).__setitem__(key, type(self)(value))
        else:
            super(DotAccessibleDict, self).__setitem__(key, value)

    def __delattr__(self, attr):
        try:
            del self[attr]
        except KeyError:
            raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")


    def get(self, key, default=None):
        if key in self:
            return super().get(key, default)
        else:
            return default

    def dict(self):
        result = {}
        for key, value in self.items():
            if isinstance(value, DotAccessibleDict):
                result[key] = value.dict()
            else:
                result[key] = value
        return result

    def json(self):
        return json.dumps(self.dict(), ensure_ascii=False)

 

Explanation

This class works by creating a new instance of itself every time a non-existent attribute is accessed. This means that we can keep chaining attribute accesses and create deeply nested structures with ease.

Moreover, when setting a value, if the value is a dictionary, we convert it to an instance of DotAccessibleDict. This ensures that nested dictionaries also support dot-accessible keys.

We also override the __setitem__ method to ensure that nested dictionaries are converted to DotAccessibleDict instances when set using index notation.

Finally, we implement dict() and json() methods that return a regular dictionary and a JSON string representation of our dictionary, respectively. The dict() method works by recursively converting all DotAccessibleDict instances to regular dictionaries.

 

반응형

Example

test.py

from dot_dict import DotAccessibleDict

## test code
if __name__ == "__main__":

    data = DotAccessibleDict()
    data.newkey.first = 1
    print(data)               # {'newkey': {'first': 1}}
    print(data.newkey.first)  # 1

    data.newkey.second = {"issue":{"message":"blahblah", "code":9}}
    print(data)                             # {'newkey': {'first': 1, 'second': {'issue': {'message': 'blahblah', 'code': 9}}}}
    print(data.newkey.second.issue.message) # 'blahblah'
    
    print(f"to dict --> {data.dict()}") 
    print(f"to json --> {data.json()}")

 

반응형