Pull to refresh

Еще немного о дескрипторах в Python

Reading time 3 min
Views 18K
Не так давно на Хабре уже был перевод статьи Раймонда Хеттингера Руководство к дескрипторам. В этой статье я постараюсь рассмотреть вопросы, которые возникли у меня после прочтения. Будет немного примеров кода, собственно вопросов и ответов к ним. Для понимания того, о чем речь, вам нужно знать, что такое дескрипторы и зачем они.

Когда вызываются дескрипторы?


Рассмотрим следующий код:

>>> 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 "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 2 arguments (1 given)


* This source code was highlighted with Source Code Highlighter.

Что не так? Вроде добавляем функцию одинаково, используя словарь объекта. Почему не вызывается дескриптор для функции «test2»? Дело в том, что функция «test» определяется для словаря класса, а функция «test2» — для словаря объекта:

>>> '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.

Отсюда — первый ответ: функция "__get__" вызывается только для дескрипторов, которые являются свойствами класса, а не свойствами объектов этого класса.

Что является дескриптором?


Предыдущий ответ сразу вызывает вопрос — что значит «только для дескрипторов»? Я ведь не создавал никаких дескрипторов, я создал только функцию!

Ответ ожидаемий — функции в Питоне являются дескрипторами(если быть точнее — дескрипторами не данных):

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


* This source code was highlighted with Source Code Highlighter.

Bound/Unbound методы


И напоследок самое вкусное. Задача:

Есть объект некоего класса, к которому необходимо динамически добавить метод, которому, конечно, должен передаваться «self» параметр. Не представляется возможным добавлять этот метод в словарь класса (мы не хотим повлиять на другие объекты).

Решить «в лоб» не выходит:

>>> 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 "<stdin>", line 1, in <module>
TypeError: add_x() takes exactly 2 arguments (1 given)


* This source code was highlighted with Source Code Highlighter.

Получаем ожидаемую ошибку, которая подтверждает первый ответ — при обращении к свойству-дескриптору объекта не используется "__get__". Что же делать?

Поиграем немного с методом "__get__" функции, которую мы только что создали:

>>> add_x.__get__
<method-wrapper '__get__' of function object at 0x800f2caa0>
>>> add_x.__get__(a)
<bound method ?.add_x of &#60;__main__.A object at 0x800f2dc50>>
>>> add_x.__get__(a, A)
<bound method A.add_x of &#60;__main__.A object at 0x800f2dc50>>
>>> add_x.__get__(None, A)
<unbound method A.add_x>


* This source code was highlighted with Source Code Highlighter.

О, кажется это то, что нам надо! В предпоследней строчке мы получили «bound» метод — именно так смотрелся бы вызов «A().add_x», если в классе «A» был бы определен метод «add_x». А в последней строчке видим «ожидаемый» результат вызова «A.add_x». Теперь мы знаем, как добавить метод к объекту:

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


* This source code was highlighted with Source Code Highlighter.

Бинго!
И так, именно метод "__get__" для функций-дескрипторов создает «bound/unbound» методы классов и объектов. И нет здесь никакой магии :)

Литература


Что еще почитать? На самом деле — немного. Есть несколько глав, в которых рассказывается о дескрипторах, в Python Data Model (implementing-descriptors), ну и можно снова вспомнить действительно хорошую статью Хеттингера (оригинал).

P.S. Русский — не родной язык, замечания прошу в личку.
Tags:
Hubs:
+43
Comments 29
Comments Comments 29

Articles