RNA RNA - 28 days ago 9
Python Question

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
?

Answer

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('Accesing descriptor on the class')
...             return self
...         print('Accessing descrtor on the instance')
...         return 'Descriptor value for instance {!r}'.format(instance)
... 
>>> class DemoClass(object):
...     foo = DemoDescriptor()
... 
>>> DemoClass.foo  # on the class
Accesing 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 descrtor on the instance
'Descriptor value for instance <__main__.DemoClass object at 0x1041d3438>'

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.

Comments