Hone logo
Hone
Problems

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:

  1. Create a Descriptor Class: Define a class (e.g., IntegerAttribute) that implements the descriptor protocol.
  2. __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.
  3. __get__ Method: Implement the __get__ method to retrieve the attribute's value.
  4. __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 TypeError with a descriptive message.
  5. 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 int types.
  • The descriptor should raise a TypeError with the message "Attribute must be an integer." for invalid assignments.
  • The descriptor must correctly store and retrieve values within the instance's __dict__.
  • Your IntegerAttribute class 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 standard AttributeError will 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.
Loading editor...
python