A little more about Python descriptors

Not so long ago on Habré there was already a translation of an article by Raymond Hettinger Guide to Descriptors . In this article I will try to consider the issues that have arisen after reading me. There will be some code examples, actually questions and answers to them. To understand what it is about, you need to know what descriptors are and why they are.

When are descriptors called?


Consider the following code:

>>> class M(type):
...   def __new__(cls, name, bases, dct):
...       dct['test'] = lambda self, x: x*2
...       return type.__new__(cls, name, bases, dct)
...
>>> class A(object):
...   def __init__(self):
...       self.__dict__['test2'] = lambda self, x: x*4
...   __metaclass__ = M
...
>>> A().test(2)
4
>>> A().test2(2)
Traceback (most recent call last):
 File "", line 1, in
TypeError: () takes exactly 2 arguments (1 given)

* This source code was highlighted with Source Code Highlighter.

What's wrong? It seems to add the function the same way using the object dictionary. Why is the handle to the test2 function not called? The fact is that the “test” function is defined for the class dictionary, and the “test2” function for the object dictionary:

>>> 'test' in A.__dict__
True
>>> 'test2' in A.__dict__
False
>>> 'test' in A().__dict__
False
>>> 'test2' in A().__dict__
True

* This source code was highlighted with Source Code Highlighter.

Hence the first answer: the function "__get__" is called only for descriptors, which are properties of the class, and not properties of objects of this class.

What is a descriptor?


The previous answer immediately raises the question - what does “descriptor-only” mean? I didn’t create any descriptors, I only created a function!

The answer is expected - functions in Python are descriptors (to be more precise, not data descriptors):

>>> def foo(x):
...   return x * 2
...
>>> '__get__' in dir(foo)
True

* This source code was highlighted with Source Code Highlighter.

Bound / Unbound Methods


And finally, the most delicious. Task:

There is an object of a certain class to which you need to dynamically add a method, to which, of course, the "self" parameter should be passed. It is not possible to add this method to the class dictionary (we do not want to affect other objects).

Solving "forehead" does not go:

>>> class A(object):
...   def __init__(self):
...       self.x = 3
...
>>> def add_x(self, add):
...   self.x += add
...   print 'Modified value: %s' % (self.x,)
...
>>> a = A()
>>> a.add_x = add_x
>>> a.x
3
>>> a.add_x(3)
Traceback (most recent call last):
 File "", line 1, in
TypeError: add_x() takes exactly 2 arguments (1 given)

* This source code was highlighted with Source Code Highlighter.

We get the expected error, which confirms the first answer - when accessing the object's descriptor property, "__get__" is not used. What to do?

Let's play a little with the __get__ method of the function we just created:

>>> add_x.__get__

>>> add_x.__get__(a)
>
>>> add_x.__get__(a, A)
>
>>> add_x.__get__(None, A)


* This source code was highlighted with Source Code Highlighter.

Oh, that seems to be what we need! In the penultimate line, we got a “bound” method - this is exactly how the call to “A (). Add_x” would look if the “add_x” method was defined in the class “A”. And in the last line we see the "expected" result of calling "A.add_x". Now we know how to add a method to an object:

>>> a.add_x = add_x.__get__(a, A)
>>> a.add_x(3)
Modified value: 6

* This source code was highlighted with Source Code Highlighter.

Bingo!
And so, it is the __get__ method for the descriptor functions that creates the “bound / unbound” methods of classes and objects. And there is no magic here :)

Literature


What else to read? In fact, a little. There are several chapters that talk about descriptors in the Python Data Model ( implementing-descriptors ), and you can again recall the really good Hettinger article ( original ).

PS Russian is not a native language, I ask for comments in PM.

Also popular now: