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

1

6

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?

Cosby answered 3/4, 2015 at 17:14 Comment(3)
Exam.math_grade will pass None as instance to __get__. There's no instance involved in that case.Capitol
might be helpful nbviewer.ipython.org/gist/ChrisBeaumont/5758381/…Falkirk
Please don't add follow-up questions into your question - if you have another question, ask it! You can always include a link back to this one.Stefansson
E
7

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.

Eighteen answered 3/4, 2015 at 17:17 Comment(8)
what about __set__? will Exam.math_grade=3 pass None to instance argument too? Is there any use case of accessing the descriptor object by returning self?Cosby
@RNA: descriptors only support setting on instances. Assigning to Exam.math_grade would replace the descriptor object with whatever the right-hand-side produces.Eighteen
that seems not true (?). let me update the question.Cosby
@RNA: The @property descriptor object heavily relies on self being returned, as otherwise you couldn't use the object in sub-classes to override a property setter or getter or deleter.Eighteen
@RNA: Perhaps you made a mistake in your test; it very much is true, see gist.github.com/mjpieters/531cb4d657d0b38b8684Eighteen
I got what you mean by "replacing the descriptor object". but still have problem understanding "The @property descriptor object heavily relies on self being returned"... could you give an example why it is good to return self?Cosby
@RNA: ClassObject.descriptorname returns whatever ClassObject.__dict__['descriptorname'].__get__(None, ClassObject) returns. Returning something other than self would give you whatever you returned instead. That's inconvenient if you need to use the descriptor object itself repeatedly.Eighteen
@RNA: see Python overriding getter without setter for an example where a property descriptor is accessed; human.name is a property descriptor object, and that invokes human.__dict__['name'].__get__(None, human).Eighteen

© 2022 - 2024 — McMap. All rights reserved.