[Tips] Enhancing Dictionary Usability with DotDict in Python

By JoeVu, at: 2024年5月1日14:24

Estimated Reading Time: 5 min read

[Tips] Enhancing Dictionary Usability with DotDict in Python
[Tips] Enhancing Dictionary Usability with DotDict in Python

Enhancing Dictionary Usability with DotDict in Python

When working with dictionaries in Python, accessing keys can sometimes feel cumbersome with the standard bracket notation. Wouldn't it be nice to access dictionary keys as attributes? In this post, we'll show you how to create a DotDict class that allows you to do just that.

Here is a blog post about DotDict class in Python: https://glinteco.com/en/post/tips-python-dotdict-class/

 

Implementing the DotDict Class

The DotDict class is a subclass of Python's built-in dict class. By overriding the __getattr__, __setattr__, and __delattr__ methods, we enable attribute-style access to dictionary keys.

class DotDict(dict):
    """DotDict class allows accessing dictionary keys as attributes."""

    def __getattr__(self, attr):
        if attr in self:
            return self[attr]
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")

    def __setattr__(self, key, value):
        self[key] = value

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

 

How It Works

  • __getattr__: Allows you to access dictionary keys as attributes. If the key does not exist, it raises an AttributeError.
  • __setattr__: Lets you set dictionary keys using attribute-style assignment.
  • __delattr__: Enables deletion of dictionary keys using the del keyword.

 

Example Usage

Let's see the DotDict class in action:

# Example usage:
my_dict = {'name': 'Joe', 'age': 30, 'city': 'Hanoi'}
dot_dict = DotDict(my_dict)

# Access dictionary keys as attributes
print(dot_dict.name)  # Output: Joe
print(dot_dict.age)   # Output: 30
print(dot_dict.city)  # Output: Hanoi

# Modify values using attributes
dot_dict.age = 31
print(dot_dict.age)   # Output: 31

# Delete an attribute (key-value pair)
del dot_dict.city
# Accessing a deleted attribute raises an AttributeError
# print(dot_dict.city)  # Uncommenting this line would raise an AttributeError

 

 

Testing the DotDict Class

To ensure our DotDict class works correctly, we can write some tests using pytest. Here's a test suite that covers various use cases:

import pytest
 

@pytest.fixture
def dot_dict():
    return DotDict({'name': 'Joe', 'age': 30, 'city': 'Hanoi'})

def test_getattr(dot_dict):
    assert dot_dict.name == 'Joe'
    assert dot_dict.age == 30
    assert dot_dict.city == 'Hanoi'

def test_setattr(dot_dict):
    dot_dict.age = 31
    assert dot_dict.age == 31
    assert dot_dict['age'] == 31

def test_delattr(dot_dict):
    del dot_dict.city
    with pytest.raises(AttributeError):
        _ = dot_dict.city
    assert 'city' not in dot_dict

def test_invalid_attribute_name(dot_dict):
    dot_dict['invalid.key'] = 'value'
    with pytest.raises(AttributeError):
        _ = dot_dict.invalid.key

def test_missing_key(dot_dict):
    with pytest.raises(AttributeError):
        _ = dot_dict.nonexistent

def test_add_new_key(dot_dict):
    dot_dict.country = 'Vietnam'
    assert dot_dict.country == 'Vietnam'
    assert dot_dict['country'] == 'Vietnam'

 


You can run these tests by navigating to the directory containing test_dotdict.py and executing:

pytest test_dotdict.py

Another tests are implemented by unittest:

import unittest
 

class TestDotDict(unittest.TestCase):
    def setUp(self):
        self.dot_dict = DotDict({'name': 'Joe', 'age': 30, 'city': 'Hanoi'})

    def test_getattr(self):
        self.assertEqual(self.dot_dict.name, 'Joe')
        self.assertEqual(self.dot_dict.age, 30)
        self.assertEqual(self.dot_dict.city, 'Hanoi')

    def test_setattr(self):
        self.dot_dict.age = 31
        self.assertEqual(self.dot_dict.age, 31)
        self.assertEqual(self.dot_dict['age'], 31)

    def test_delattr(self):
        del self.dot_dict.city
        with self.assertRaises(AttributeError):
            _ = self.dot_dict.city
        self.assertNotIn('city', self.dot_dict)

    def test_invalid_attribute_name(self):
        self.dot_dict['invalid.key'] = 'value'
        with self.assertRaises(AttributeError):
            _ = self.dot_dict.invalid.key

    def test_missing_key(self):
        with self.assertRaises(AttributeError):
            _ = self.dot_dict.nonexistent

    def test_add_new_key(self):
        self.dot_dict.country = 'Vietnam'
        self.assertEqual(self.dot_dict.country, 'Vietnam')
        self.assertEqual(self.dot_dict['country'], 'Vietnam')


python -m unittest test_dotdict.py

These will run the unittest/pytest frameworks, executing the tests and providing feedback on their success or failure.

 

Conclusion

The DotDict class is a simple and elegant solution for accessing dictionary keys as attributes. It can make your code cleaner and more intuitive. However, be mindful of its limitations and consider if it fits your use case. If you encounter keys that are not valid attribute names, you may need to stick with the traditional dictionary access methods or use a different approach.


Related

Subscribe

Subscribe to our newsletter and never miss out lastest news.