Python > Advanced Python Concepts > Descriptors > Data and Non-Data Descriptors
Data and Non-Data Descriptors in Python
This snippet demonstrates the difference between data and non-data descriptors in Python and how they affect attribute access.
Concepts Behind Descriptors
Descriptors are a powerful feature in Python that allow you to customize attribute access. They're objects that implement the descriptor protocol, which consists of the methods __get__
, __set__
, and __delete__
. A data descriptor defines both __get__
and __set__
(or __delete__
), while a non-data descriptor defines only __get__
. The key difference lies in the precedence during attribute lookup. Data descriptors take precedence over instance attributes, while non-data descriptors do not.
Data Descriptor Example
In this example, DataDescriptor
defines both __get__
and __set__
. When you access obj.x
, the __get__
method of the descriptor is called. When you assign a value to obj.x
, the __set__
method is triggered. Notice that the value isn't directly stored in the instance's __dict__
; instead, the descriptor manages it.
class DataDescriptor:
def __init__(self, value=None):
self._value = value
def __get__(self, instance, owner):
print('DataDescriptor.__get__ called')
return self._value
def __set__(self, instance, value):
print('DataDescriptor.__set__ called')
self._value = value
class MyClass:
x = DataDescriptor(10)
obj = MyClass()
print(obj.x) # Accessing x triggers DataDescriptor.__get__
obj.x = 20 # Setting x triggers DataDescriptor.__set__
print(obj.x)
print(obj.__dict__)
Non-Data Descriptor Example
Here, NonDataDescriptor
only defines __get__
. When you access obj2.y
, the __get__
method is called. However, when you assign a value to obj2.y
, a new attribute y
is created in the instance's __dict__
. This new attribute shadows the descriptor, meaning that subsequent accesses to obj2.y
will retrieve the instance attribute and no longer call the descriptor's __get__
method.
class NonDataDescriptor:
def __init__(self, value=None):
self._value = value
def __get__(self, instance, owner):
print('NonDataDescriptor.__get__ called')
return self._value
class MyClass2:
y = NonDataDescriptor(30)
obj2 = MyClass2()
print(obj2.y) # Accessing y triggers NonDataDescriptor.__get__
obj2.y = 40 # Instance attribute 'y' is created, shadowing the descriptor
print(obj2.y) # Accessing y now retrieves the instance attribute
print(obj2.__dict__)
Real-Life Use Case Section
Descriptors are widely used in Python's internals. Properties, methods, static methods, and class methods are all implemented using descriptors. They are useful for implementing data validation, lazy loading, and calculated attributes. For example, a property can validate that an attribute is within a specific range before allowing it to be set.
Best Practices
Interview Tip
Be prepared to explain the descriptor protocol and the difference between data and non-data descriptors. You should be able to provide examples of when you might use each type. A common interview question involves implementing a read-only attribute using descriptors.
When to Use Them
Use descriptors when you need to encapsulate and control attribute access behavior. This is particularly useful for implementing calculated properties, validation logic, and lazy loading.
Memory Footprint
Descriptors themselves generally don't add significant memory overhead. However, if they manage large data structures, the associated memory usage can be significant. Consider using techniques like weak references or memoization to optimize memory usage when dealing with descriptors that handle large datasets.
Alternatives
Alternatives to descriptors include using properties (which are built upon descriptors), implementing custom getter and setter methods directly on the class, or using metaclasses for more advanced attribute management. However, descriptors often offer a cleaner and more modular approach for complex attribute access control.
Pros
Cons
FAQ
-
What is the difference between a data descriptor and a non-data descriptor?
A data descriptor has both__get__
and__set__
(or__delete__
) methods, while a non-data descriptor only has a__get__
method. Data descriptors take precedence over instance attributes during attribute lookup, while non-data descriptors do not. -
When should I use a descriptor?
Use descriptors when you need to control how an attribute is accessed or modified, such as for validation, lazy loading, or calculated properties. -
How do I create a read-only attribute using descriptors?
Create a data descriptor with a__get__
method that returns the attribute's value but without a__set__
method. This will prevent external modification of the attribute.