Data descriptor defines both __get__ and __set__. Non-data descriptor only defines __get__.
1# Data descriptor (takes priority over instance dict)2class DataDescriptor:3 def __set_name__(self, owner, name):4 self.name = name56 def __get__(self, obj, objtype=None):7 if obj is None:8 return self9 return obj.__dict__.get(f"_{self.name}")1011 def __set__(self, obj, value):12 obj.__dict__[f"_{self.name}"] = value1314# Non-data descriptor (instance dict takes priority)15class NonDataDescriptor:16 def __set_name__(self, owner, name):17 self.name = name1819 def __get__(self, obj, objtype=None):20 if obj is None:21 return self22 return lambda: f"from {self.name}"2324class MyClass:25 data = DataDescriptor()26 non_data = NonDataDescriptor()2728m = MyClass()29m.data = 42 # Uses descriptor30print(m.data) # 42 (from descriptor)31m.non_data = 42 # Stored in instance dict32print(m.non_data) # 42 (from instance dict)
Priority:
property is a data descriptor.