[Tips] Enhancing Dictionary Usability with DotDict in Python
By JoeVu, at: 2024年5月1日14:24
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 anAttributeError
.__setattr__
: Lets you set dictionary keys using attribute-style assignment.__delattr__
: Enables deletion of dictionary keys using thedel
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.