Data Descriptor vs Non Data Descriptor in Python: Key Differences
data descriptor defines both __get__ and __set__ methods, controlling attribute access and assignment, while a non-data descriptor defines only __get__, allowing attribute assignment to override it. Data descriptors have higher priority in attribute lookup than instance variables, unlike non-data descriptors.Quick Comparison
This table summarizes the main differences between data descriptors and non-data descriptors in Python.
| Aspect | Data Descriptor | Non-Data Descriptor |
|---|---|---|
| Methods Implemented | __get__ and __set__ (or __delete__) | Only __get__ |
| Attribute Control | Controls both access and assignment | Controls only access |
| Priority in Lookup | Overrides instance variables | Can be overridden by instance variables |
| Use Case | For managed attributes needing validation or computed values | For read-only or computed attributes |
| Example | Property with setter | Method or cached property without setter |
Key Differences
A data descriptor in Python is a class that implements both __get__ and __set__ methods (or __delete__). This means it can control what happens when you read, write, or delete an attribute. Because of this, data descriptors take priority over instance variables during attribute lookup. This makes them ideal for managing attributes that require validation, computed values, or other side effects when changed.
On the other hand, a non-data descriptor only implements __get__. It controls what happens when you access an attribute but does not control assignment. If you assign a value to the attribute on the instance, it will override the descriptor. This behavior is common for methods or read-only computed properties.
In summary, the main difference lies in whether the descriptor controls attribute assignment (data descriptor) or only attribute access (non-data descriptor). This affects how Python looks up attributes and whether instance variables can override the descriptor.
Code Comparison
Here is an example of a data descriptor that manages an attribute with validation on assignment.
class DataDescriptor: def __init__(self): self.value = None def __get__(self, instance, owner): print("DataDescriptor __get__ called") return self.value def __set__(self, instance, value): print("DataDescriptor __set__ called") if not isinstance(value, int): raise ValueError("Value must be an integer") self.value = value class MyClass: attr = DataDescriptor() obj = MyClass() obj.attr = 10 # Calls __set__ print(obj.attr) # Calls __get__ # obj.attr = 'hello' # Would raise ValueError
Non-Data Descriptor Equivalent
This example shows a non-data descriptor that only implements __get__. Assignment to the attribute on the instance overrides the descriptor.
class NonDataDescriptor: def __get__(self, instance, owner): print("NonDataDescriptor __get__ called") return 42 class MyClass: attr = NonDataDescriptor() obj = MyClass() print(obj.attr) # Calls __get__ obj.attr = 100 # Overrides descriptor on instance print(obj.attr) # Prints instance attribute, no __get__ call
When to Use Which
Choose a data descriptor when you need to control both reading and writing of an attribute, such as enforcing type checks, computed values, or side effects on assignment. This ensures your logic always runs and cannot be bypassed by instance variables.
Choose a non-data descriptor when you only need to control attribute access, like for methods or read-only computed properties, and you want to allow instance variables to override the descriptor if needed.