gkb0986 gkb0986 - 1 month ago 9
Python Question

Descriptors and direct access: Python reference

The python 3.3 documentation tells me that direct access to a property descriptor should be possible, although I'm skeptical of its syntax

x.__get__(a)
. But the example that I constructed below fails. Am I missing something?

class MyDescriptor(object):
"""Descriptor"""
def __get__(self, instance, owner):
print "hello"
return 42

class Owner(object):
x = MyDescriptor()
def do_direct_access(self):
self.x.__get__(self)

if __name__ == '__main__':
my_instance = Owner()
print my_instance.x
my_instance.do_direct_access()


Here's the error I get in Python 2.7 (and also Python 3.2 after porting the snippet of code). The error message makes sense to me, but that doesn't seem to be how the documentation said it would work.

Traceback (most recent call last):
File "descriptor_test.py", line 15, in <module>
my_instance.do_direct_access()
File "descriptor_test.py", line 10, in do_direct_access
self.x.__get__(self)
AttributeError: 'int' object has no attribute '__get__'

shell returned 1

Answer

By accessing the descriptor on self you invoked __get__ already. The value 42 is being returned.

For any attribute access, Python will look to the type of the object (so type(self) here) to see if there is a descriptor object there (an object with a .__get__() method, for example), and will then invoke that descriptor.

That's how methods work; a function object is found, which has a .__get__() method, which is invoked and returns a method object bound to self.

If you wanted to access the descriptor directly, you'd have to bypass this mechanism; access x in the __dict__ dictionary of Owner:

>>> Owner.__dict__['x']
<__main__.MyDescriptor object at 0x100e48e10>
>>> Owner.__dict__['x'].__get__(None, Owner)
hello
42

This behaviour is documented right above where you saw the x.__get__(a) direct call:

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.

The Direct Call scenario in the documentation only applies when you have a direct reference to the descriptor object (not invoked); the Owner.__dict__['x'] expression is such a reference.

Your code on the other hand, is an example of the Instance Binding scenario:

Instance Binding
If binding to an object instance, a.x is transformed into the call: type(a).__dict__['x'].__get__(a, type(a)).