Implementing the Descriptor Protocol in Python
The descriptor protocol in Python is a powerful mechanism that allows you to customize attribute access for class attributes. By implementing this protocol, you can gain fine-grained control over how attributes are retrieved, set, and deleted. This challenge will guide you through creating your own descriptor to manage a specific type of attribute.
Problem Description
Your task is to implement a Python class that acts as a descriptor. This descriptor should manage an integer attribute within another class. Specifically, the descriptor should enforce that the attribute can only be assigned integer values. If a non-integer value is attempted to be assigned, it should raise a TypeError. When the attribute is accessed, it should return its current integer value.
Key Requirements:
- Create a Descriptor Class: Define a class (e.g.,
IntegerAttribute) that implements the descriptor protocol. __set_name__Method: Implement the__set_name__method to store the name of the attribute being managed by the descriptor. This is crucial for knowing which attribute to store the value for within the instance.__get__Method: Implement the__get__method to retrieve the attribute's value.__set__Method: Implement the__set__method to assign a value to the attribute. This method must:- Check if the value being assigned is an integer.
- If it's an integer, store it.
- If it's not an integer, raise a
TypeErrorwith a descriptive message.
- Attribute Storage: The descriptor should store the actual integer values in the instance's
__dict__.
Expected Behavior:
- When an instance of a class using your descriptor is created, the attribute managed by the descriptor should not initially exist.
- Accessing the attribute before it's set should raise an
AttributeError(standard Python behavior for unset attributes). - Setting the attribute to an integer should succeed.
- Setting the attribute to a non-integer should raise a
TypeError. - Accessing the attribute after it has been successfully set should return the assigned integer value.
Examples
Example 1:
class MyClass:
count = IntegerAttribute()
# --- Usage ---
obj = MyClass()
# Setting a valid integer
obj.count = 10
print(obj.count)
# Setting another valid integer
obj.count = 25
print(obj.count)
Output:
10
25
Explanation:
The IntegerAttribute descriptor correctly handles the assignment and retrieval of integer values for the count attribute.
Example 2:
class DataProcessor:
threshold = IntegerAttribute()
# --- Usage ---
processor = DataProcessor()
# Attempting to set a string
try:
processor.threshold = "high"
except TypeError as e:
print(e)
# Attempting to set a float
try:
processor.threshold = 5.5
except TypeError as e:
print(e)
# Accessing before setting (will raise AttributeError if not previously set by a valid assignment)
# try:
# print(processor.threshold)
# except AttributeError as e:
# print(e)
# Setting a valid integer after failed attempts
processor.threshold = 100
print(processor.threshold)
Output:
Attribute must be an integer.
Attribute must be an integer.
100
Explanation:
The descriptor correctly raises TypeError when non-integer values are assigned. After a valid integer assignment, the attribute can be accessed.
Example 3: Using the descriptor on multiple instances
class Counter:
value = IntegerAttribute()
# --- Usage ---
counter1 = Counter()
counter2 = Counter()
counter1.value = 5
counter2.value = 15
print(f"Counter 1 value: {counter1.value}")
print(f"Counter 2 value: {counter2.value}")
# Check if changing one instance affects the other
counter1.value = 10
print(f"Counter 1 new value: {counter1.value}")
print(f"Counter 2 value: {counter2.value}")
Output:
Counter 1 value: 5
Counter 2 value: 15
Counter 1 new value: 10
Counter 2 value: 15
Explanation:
Each instance of Counter has its own independent value attribute, managed correctly by the shared IntegerAttribute descriptor.
Constraints
- The descriptor should only allow assignment of Python
inttypes. - The descriptor should raise a
TypeErrorwith the message "Attribute must be an integer." for invalid assignments. - The descriptor must correctly store and retrieve values within the instance's
__dict__. - Your
IntegerAttributeclass should be a single, reusable descriptor.
Notes
- Remember that descriptors have access to the instance (
obj) and the owner class (owner) in their__get__and__set__methods. - The
__set_name__method is called by Python when the descriptor is assigned to a class attribute. It receives the name of the attribute as an argument. - When implementing
__get__, you'll need to retrieve the value from the instance's__dict__using the name stored during__set_name__. If the attribute isn't found in the instance's__dict__, a standardAttributeErrorwill be raised by Python's attribute lookup mechanism, which is the desired behavior here. - Think about how to store the attribute's value without the descriptor overwriting itself or interfering with other attributes. The instance's
__dict__is your friend here.