class CustomProperty:
    def __init__(self, fget=None, fset=None):
        self.fget = fget
        self.fset = fset

    def __set_name__(self, owner_class, prop_name):
        self.prop_name = prop_name
    
    def __get__(self, instance, owner_class):
        print('__get__ called...')
        if instance is None:
            return self
        if self.fget is None:
            raise AttributeError(f'{self.prop_name} is not readble.')
        return self.fget(instance)
    
    def __set__(self, instance, value):
        print('__set__ called...')
        if self.fset is None:
            raise AttributeError(f'{self.prop_name} is not writable.')
        self.fset(instance, value)

    def setter(self, fset):
        self.fset = fset
        return self
    

class Person:
    @CustomProperty
    def first_name(self):
        return getattr(self, '_first_name', None)
    
    @first_name.setter
    def first_name(self, value):
        self._first_name = value

    @CustomProperty
    def last_name(self):
        return getattr(self, '_last_name', None)
    
    @last_name.setter
    def last_name(self, value):
        self._last_name = value


def test():
    p = Person()
    print(p.__dict__)
    p.name = 'Artem'
    print(p.name)
    print(p.__dict__)

    # пробуем затемнить дескриптор
    p.__dict__['name'] = 'Egor'
    print(p.name)



def main():
    test()


if __name__ == '__main__':
    main()