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