Implementing the Descriptor Protocol in Python
The descriptor protocol in Python allows you to control attribute access (getting, setting, and deleting) for classes. This challenge asks you to implement a custom descriptor that enforces a specific constraint on the values assigned to an attribute, demonstrating your understanding of the protocol's core components. This is useful for creating more robust and controlled classes, preventing invalid data from being assigned to attributes.
Problem Description
You are tasked with creating a descriptor class called PositiveInteger. This descriptor should ensure that any value assigned to an attribute using this descriptor is a positive integer. If a non-positive integer or any other data type is assigned, the assignment should be prevented and an appropriate error message should be raised.
The PositiveInteger descriptor should implement the following parts of the descriptor protocol:
__get__(self, instance, owner): This method is called when the attribute is accessed (e.g.,instance.my_attribute). It should return the value of the attribute. If the attribute hasn't been set, it should returnNone.__set__(self, instance, value): This method is called when the attribute is assigned a value (e.g.,instance.my_attribute = 10). It should check if thevalueis a positive integer. If it is, it should set the attribute's value on the instance. If not, it should raise aTypeErrorwith a descriptive message.__delete__(self, instance): This method is called when the attribute is deleted (e.g.,del instance.my_attribute). It should delete the attribute from the instance.
Examples
Example 1:
Input:
class MyClass:
my_attribute = PositiveInteger()
instance = MyClass()
instance.my_attribute = 5
print(instance.my_attribute)
Output:
5
Explanation: A positive integer (5) is assigned to the attribute, and the descriptor allows the assignment and returns the value when accessed.
Example 2:
Input:
class MyClass:
my_attribute = PositiveInteger()
instance = MyClass()
try:
instance.my_attribute = -2
except TypeError as e:
print(e)
Output:
Value must be a positive integer.
Explanation: A non-positive integer (-2) is assigned, and the descriptor raises a TypeError as expected.
Example 3:
Input:
class MyClass:
my_attribute = PositiveInteger()
instance = MyClass()
instance.my_attribute = 10
del instance.my_attribute
try:
print(instance.my_attribute)
except AttributeError as e:
print(e)
Output:
AttributeError: 'MyClass' object has no attribute 'my_attribute'
Explanation: The attribute is initially set, then deleted. Attempting to access it after deletion raises an AttributeError.
Constraints
- The descriptor must correctly handle positive integers (including 0 is not allowed).
- The descriptor must raise a
TypeErrorif a non-positive integer is assigned. - The descriptor must raise an
AttributeErrorif the attribute is accessed or assigned after being deleted. - The descriptor should not raise any other exceptions during normal operation.
- The code should be well-structured and readable.
Notes
- Remember that the
__get__,__set__, and__delete__methods are called on the descriptor instance, not the instance of the class using the descriptor. - The
ownerargument in__get__and__set__refers to the class that defines the descriptor. - Consider using the
isinstance()function to check the type of the assigned value. - The descriptor should only enforce the positive integer constraint; it should not perform any other validation.
- Focus on implementing the core descriptor protocol methods correctly.