Python descriptor

Suppose you create a class 'Book' and an attribute 'name'. You create an object of that class. With object, you can directly get or set the name attribute. You could also set any value without any validation.

class Book:
  def __init__(self):
    pass
python_book = Book()
book.name = ""

To avoid setting invalid values, you need a way of validating the user input. To do so, you create getter and setter methods. These methods perform validation and then get or set the value of name.

class Book:
  def __init__(self):
    self.name = ""
  def get_name(self):
    return self.name
  def set_name(self,value):
    if not value:
      raise ValueError('name can not be null or empty')
    self.name=value


python_book = Book()
python_book.set_name("")

>>> python_book.set_name("")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in set_name
ValueError: name can not be null or empty
>>>

Now you can not set an invalid value to book's name. Similarly, you can also add some validation while reading the name in get_name method. This would solve the problem partially since you could still set the name attribute directly if you want to and that will bypass all the validation that you have put in place.

That is when the python descriptor comes to help.

  • Python descriptors are objects which implement __get__(), __set__() or __delete__() methods.
  • Descriptor object is assigned to class attribute.
  • When an attribute , which is assigned a descriptor, is looked up using . , descriptor object is returned from the class dictionary.
  • Python interpreter recognize the descriptor object and depending on the operation (read/write/delete), corresponding descriptor method (__get__/ __set__/__delete__) is called.

using a descriptor.

  • First we create a class which represent out descriptor
  • Create an object of descriptor class and assign to class variable.
#  First we create a class for descriptor
class Name:
  def __get__(self,obj,objtype=None):
    print('Reading name')
    return self._name
  def __set__(self,obj,value):
    if not value:
      raise ValueError("Name can not be null or empty")
    print('setting name')
    obj._name=value

# Create class for Book and assign Name descriptor's 
# object to name class attribute. 
class Book:
  name = Name() # Descriptor object is assigned to class attribute 
  def __init__(self,name):
    self.name=name
  • Here in descriptor methods, self points to the descriptor object itself.
  • obj points to the object on which attribute is defined i.e. in this case, object of book class.

During the look up for name using self.name, this attribute is found in class' dictionary as per the look up rules.This attribute is found to be a descriptor type and corresponding __get__ or __set__ method is called.

When name attribute is set, descriptor's __set__ method is called. These descriptor methods are good place to implement any validation before performing the actual (read/write/delete) operation.

#  First we create a class for descriptor
class Name:
  def __get__(self,obj,objtype=None):
    print('reading name')
    return obj._name
  def __set__(self,obj,value):
    if not value:
      raise ValueError("name can not be null")
    print('setting book name')
    obj._name=value

# Create class for Book and assign Name descriptor's 
# object to name class attribute. 
class Book:
  name = Name()
  def __init__(self,name):
    self.name=name

>>> book= Book('Python')
setting book name
>>> print(book.name)
reading name
Python
>>> book.name = "Python programming"
setting book name
>>>

This is a very simplified example of descriptor to explain the concept. Descriptor are heavily used on python. Once of the use case is implementing properties which we will look into later .

Summary

This article does not provide complete details about descriptor but just introduces the concept of it. Takeaway from this are -

  1. Descriptor implements get, set or delete methods.
  2. Descriptor object is always assigned to class variable.
  3. If a variable is assigned a descriptor object, read/write/delete operation invokes get/set/delete operation on descriptor object.

We will look descriptor in more details and other use cases in next post.