Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why do you need "if instance is None" in __get__ of a descriptor class?

I get the following example from Effective Python item 31:

from weakref import WeakKeyDictionary
class Grade(object):
    def __init__(self):
        self._values = WeakKeyDictionary()
    def __get__(self, instance, instance_type):
        if instance is None: return self
        return self._values.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        self._values[instance] = value


# Example 16
class Exam(object):
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()

first_exam = Exam()
first_exam.writing_grade = 82
second_exam = Exam()
second_exam.writing_grade = 75
print('First ', first_exam.writing_grade, 'is right')
print('Second', second_exam.writing_grade, 'is right')

I can't think of any reason to have if instance is None: return self in __get__. How can an Exam (or other potential classes using Grade) instance be None?

like image 475
RNA Avatar asked Apr 03 '15 17:04

RNA


1 Answers

Python will pass in None for the instance when accessing the descriptor on the class.

By returning self in that case you can access the descriptor object on the class without having to bypass the protocol (by accessing ClassObj.__dict__['name_of_descriptor']).

>>> class DemoDescriptor:
...     def __get__(self, instance, type_):
...         if instance is None:
...             print('Accessing descriptor on the class')
...             return self
...         print('Accessing descriptor on the instance')
...         return 'Descriptor value for instance {!r}'.format(instance)
... 
>>> class DemoClass(object):
...     foo = DemoDescriptor()
... 
>>> DemoClass.foo  # on the class
Accessing descriptor on the class
<__main__.DemoDescriptor object at 0x1041d3c50>
>>> DemoClass.__dict__['foo']  # bypassing the descriptor protocol
<__main__.DemoDescriptor object at 0x1041d3c50>
>>> DemoClass().foo  # on the instance
Accessing descriptor on the instance
'Descriptor value for instance <__main__.DemoClass object at 0x1041d3438>'

This is how the __get__ method implementations for function and property objects work too.

For your specific case, each of Exam.math_grade, Exam.writing_grade or Exam.science_grade will call Grade.__get__, passing in None for the instance, and Exam for the instance_type.

like image 118
Martijn Pieters Avatar answered Sep 28 '22 18:09

Martijn Pieters