PythiloLesson 16 of 30 · progress kept in this browser

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__ receives obj=None when 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).