Here's my version of @derek73 answer. I use dict.__getitem__
as __getattr__
so it still throws KeyError
, and im renaming dict public methods with "" prefix (surrounded with "" leads to special methods name conflict, like __get__
which would be treated as a descriptor method). Anyway you can't get completely clear namespace for keys as attributes due to crucial dict
base methods, so the solution isn't perfect but you can have keys - attributes like get
, pop
, items
etc.
class DotDictMeta(type): def __new__( cls, name, bases, attrs, rename_method=lambda n: f'__{n}__', **custom_methods, ): d = dict attrs.update( cls.get_hidden_or_renamed_methods(rename_method), __getattr__=d.__getitem__, __setattr__=d.__setitem__, __delattr__=d.__delitem__, **custom_methods, ) return super().__new__(cls, name, bases, attrs) def __init__(self, name, bases, attrs, **_): super().__init__(name, bases, attrs) @property def attribute_error(self): raise AttributeError @classmethod def get_hidden_or_renamed_methods(cls, rename_method=None): public_methods = tuple( i for i in dict.__dict__.items() if not i[0].startswith('__') ) error = cls.attribute_error hidden_methods = ((k, error) for k, v in public_methods) yield from hidden_methods if rename_method: renamed_methods = ((rename_method(k), v) for k, v in public_methods) yield from renamed_methods class DotDict(dict, metaclass=DotDictMeta): pass
You can remove dict methods from DotDict namespace and keep using dict class methods, its useful also when you want to operate on other dict instances and want to use the same methods without extra check whether its DotDict or not, eg.
dct = dict(a=1)dot_dct = DotDict(b=2)foo = {c: i for i, c in enumerate('xyz')}for d in (dct, dot_dct): # you would have to use dct.update and dot_dct.__update methods dict.update(d, foo)assert dict.get(dot, 'foo', 0) is 0