Descriptors
property is built on a general hook. A descriptor is an object, stored on a class, that defines __get__ and often __set__. Reading or assigning that attribute on an instance calls those methods instead.
class Positive:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, owner=None):
if obj is None:
return self
return obj.__dict__[self.name]
def __set__(self, obj, value):
if value <= 0:
raise ValueError(self.name)
obj.__dict__[self.name] = value
class Box:
width = Positive()
height = Positive()
def __init__(self, w, h):
self.width, self.height = w, h
print(Box(2, 3).height, vars(Box(2, 3)))__set_name__runs once, when the class is created, and tells the descriptor its attribute name.__get__receivesobj=Nonewhen the attribute is read on the class itself. Returning the descriptor then keeps introspection working.- A descriptor with
__set__takes priority over the instance's__dict__, so storing the value there under the same name is safe.
One descriptor class gives the same rule to any number of attributes, where properties need a getter and setter each. Functions, classmethod and staticmethod are descriptors too.
Your task
Write a descriptor Typed(kind) that stores each value in the instance's __dict__ under the attribute's own name, raises TypeError for a value that is not an instance of kind, and returns itself when read from the class. Person(name, age) uses name = Typed(str) and age = Typed(int).
The explanation opens here once every check passes.