It's possible to automate things to some degree, as in the sense of eliminating a lot of repetitive coding. In the code below this is being done by utilizing a combination of a utility function that creates the properties along with a metaclass that post-processes the class definition and to initialize the dictionary you also want it to also have.
The latter involves searching for those that were defined and initializing both the instance storage attribute associated with their property name as well as the corresponding item in this additional dictionary. This requirement is what is making the creation of a class instance as complicated as it is, especially the need to have a metaclass — so you might want to reconsider whether that's really necessary or not.
def dict_property(name): """ Define property with given name that updates a private attribute and the instance's dictionary named some_dict. """ storage_name = '_' + name @property def prop(self): return getattr(self, storage_name) @prop.setter def prop(self, value): setattr(self, storage_name, value) self.some_dict.update({name: value}) return prop class MyMetaClass(type): def __new__(cls, name, bases, classdict, **kwargs): """ Initialize dict_property values in class and some_dict to None. """ names = [] for name, value in classdict.items(): if isinstance(value, property): #print(f'found property named {name!r}') names.append(name) some_dict = {} for name in names: storage_name = '_' + name classdict[storage_name] = None some_dict[name] = None classdict['some_dict'] = some_dict # Add dict to class. # Create the class. return type.__new__(cls, name, bases, classdict, **kwargs) class Test(metaclass=MyMetaClass): id = dict_property('id') age = dict_property('age') tst = Test() print(f'{tst.id=}') # -> tst.id=None print(f'{tst.age=}') # -> tst.age=None print(f'{tst.some_dict=}') # -> tst.some_dict={'id': None, 'age': None} print() tst.id = 112 print(f'{tst.id=}') # -> tst.id=112 print(f'{tst.age=}') # -> tst.age=None print(f'{tst.some_dict=}') # -> tst.some_dict={'id': 112, 'age': None} print() tst.age = 42 print(f'{tst.id=}') # -> tst.id=112 print(f'{tst.age=}') # -> tst.age=42 print(f'{tst.some_dict=}') # -> tst.some_dict={'id': 112, 'age': 42}
some_dicta@propertyas well and always create it on-the-fly.self._some_dictin your code. (typo?) You are using getters and setters without doing anything special in them, simply useself.idinstead - unless you really need a getter/setter for logic . python works just fine without it - and it is simpler/easier to understandself._id = Nonein your class and instead pull/put all your values (via getter/setter) from the dict that you use as "generic" backingfield - but that makes not much sense as the class already has a dict that stores all its members ...