Понимание примера дескриптора из статьи с практическими рекомендациями по дескрипторам

Я пытаюсь осмыслить концепцию дескрипторов, и в последнее время мне это не удалось. Эта статья дескриптора How to действительно помогла и в то же время тоже меня смутил. Я борюсь с этим примером здесь, почему m.x вызывает def __get__(self, obj, objtype):

class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving', self.name)
        return self.val

    def __set__(self, obj, val):
        print('Updating', self.name)
        self.val = val

>>> class MyClass(object):
...     x = RevealAccess(10, 'var "x"')
...     y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10

Я считаю, что причина, по которой я борюсь с этим, заключается в том, что меня смущает следующее утверждение в статье.

Детали вызова зависят от того, является ли obj объектом или классом. Для объектов механизм находится в object.__getattribute__(), который преобразует b.x в type(b).__dict__['x'].__get__(b, type(b)). Реализация работает через цепочку приоритетов, которая дает дескрипторам данных приоритет над переменными экземпляра, приоритет переменных экземпляра над дескрипторами, не относящимися к данным, и назначает самый низкий приоритет __getattr__(), если он предоставлен.

Я не уверен, что автор имеет в виду здесь под объектом или классом. Означает ли объект экземпляр класса? Я понимаю, что когда мы делаем instance.var, python внутри делает instance.__dict__["var"], если он не найден, то он делает class.__dict__["var"]. Я немного потерял его после этой концепции. Может ли кто-нибудь объяснить немного о том, как работает этот пример. Как определение def __get__(self, obj, objtype): вызывается с помощью m.x. Буду очень признателен, если кто-нибудь прояснит это.


person James Franco    schedule 08.11.2016    source источник
comment
Если вы используете имена параметров: def __get__(self, instance, owner), это может быть понятнее. RevealAccess — это класс дескриптора, а MyClass — класс-владелец. Имейте в виду, что классы являются объектами, поэтому owner будет объектом класса MyClass.   -  person cdarke    schedule 08.11.2016


Ответы (2)


Да, под объектом автор подразумевает экземпляр, а не класс. Различие происходит от того, где живет дескриптор; дескриптор определен в классе, поэтому при доступе к этому дескриптору непосредственно в классе используется другой путь, чем при доступе к этому дескриптору в экземпляре класса.

В вашем примере m является экземпляром. Сам этот экземпляр не имеет атрибута m ('x' in m.__dict__ равен False). Python также обращается к type(m) для разрешения этого атрибута, а type(m) здесь равно MyClass. 'x' in MyClass.__dict__ — это True, а MyClass.__dict__['x'].__get__ существует, поэтому Python теперь знает, что этот объект является дескриптором.

Итак, поскольку m.__dict__['x'] нет, но есть type(m).__dict__['x'].__get__, этот метод вызывается с m и type(m) в качестве аргументов, что приводит к type(m).__dict__['x'].__get__(m, type(m)).

Если 'x' in MyClass.__dict__ истинно, но MyClass.__dict__['x'].__get__ не существует (поэтому объект не является объектом-дескриптором), то MyClass.__dict__['x'] будет возвращено напрямую.

Ситуация становится более интересной, если вы попытаетесь добавить атрибут x к экземпляру тоже. В этом случае важно, существуют ли MyClass.__dict__['x'].__set__ или MyClass.__dict__['x'].__delete__, что делает дескриптор дескриптором data. Дескрипторы данных всегда выигрывают в случае ничьей. Таким образом, если MyClass.__dict__['x'].__get__ и хотя бы один из MyClass.__dict__['x'].__set__ или MyClass.__dict__['x'].__delete__ существует, уже не имеет значения, существует ли также m.__dict__['x']. Python даже не будет его искать.

Однако если в объекте дескриптора класса отсутствуют методы __set__ или __delete__, то m.__dict__['x'] выигрывает и возвращается. В этом случае дескриптор является обычным дескриптором без данных и проигрывает атрибуту экземпляра.

И последнее, но не менее важное: если 'x' in type(m).__dict__ ложно (в классе нет объекта), то возвращается m.__dict__['m']. Протокол дескриптора применяется только к объектам класса, но не к атрибутам экземпляра.

person Martijn Pieters    schedule 08.11.2016
comment
Не могли бы вы также объяснить, что произошло бы, если бы x была переменной экземпляра в MyClass, а не переменной класса? (т.е.) if m ('x' in m.__dict__ is True). - person James Franco; 08.11.2016
comment
@JamesFranco: в дополнение к MyClass.x существующему? - person Martijn Pieters; 08.11.2016
comment
Нет, если MyClass.x не существовало, а вместо этого было InstanceOfMyClass.x (т.е. если x был объявлен в методе init как self.x. - person James Franco; 08.11.2016
comment
@JamesFranco: в этом случае конфликта нет. Дескрипторы поддерживаются только не классом, а не экземпляром, поэтому m.__dict__['x'] возвращается напрямую. - person Martijn Pieters; 08.11.2016
comment
Теперь я думаю, что понял. Спасибо. - person James Franco; 08.11.2016

Означает ли объект экземпляр класса?

Да.

Как определение def __get__(self, obj, objtype): вызывается с помощью m.x.

Из-за того, что в цитируемой вами документации говорится:

Для объектов механизм находится в object.__getattribute__(), который преобразует b.x в type(b).__dict__['x'].__get__(b, type(b)).

object.__getattribute__ — это механизм, который реализует то, что происходит, когда вы делаете что-то вроде m.x. Итак, явно:

m.x
type(m).__dict__['x'].__get__(m, type(m))
MyClass.__dict__['x'].__get__(m, MyClass)
RevealAccess(10, 'var "x"').__get__(m, MyClass)

поэтому последняя строка вызывает метод __get__ класса RevealAccess.

person BrenBarn    schedule 08.11.2016