Простое воспроизведение:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
class B(object):
v = VocalDescriptor()
B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor
У этого вопроса есть эффективный дубликат , но на него не было ответа, и я немного углубился в источник CPython в качестве учебного упражнения. Предупреждение: я пошел в сорняки. Я действительно надеюсь, что смогу получить помощь от капитана, который знает эти воды . Я старался быть максимально откровенным в отслеживании вызовов, на которые я смотрел, для своей будущей выгоды и выгоды будущих читателей.
Я видел много чернил, пролитых по поведению, __getattribute__
примененному к дескрипторам, например, приоритет поиска. Python фрагмент кода в «Вызов Дескрипторы» чуть ниже For classes, the machinery is in type.__getattribute__()...
примерно соглашается на мой взгляд , с тем, что я считаю, соответствующий CPython источник в type_getattro
, который я разыскал, глядя на «tp_slots» , то где tp_getattro населен . И то, что B.v
изначально печатает, __get__, obj=None, objtype=<class '__main__.B'>
имеет для меня смысл.
Что я не понимаю, так это то, почему назначение B.v = 3
слепо перезаписывает дескриптор, а не срабатывает v.__set__
? Я попытался отследить вызов CPython, начиная еще раз с «tp_slots» , затем глядя на то, где заполнено tp_setattro , затем на type_setattro . type_setattro
кажется тонкой оболочкой вокруг _PyObject_GenericSetAttrWithDict . И в этом суть моего заблуждения: _PyObject_GenericSetAttrWithDict
кажется, есть логика, которая отдает приоритет __set__
методу дескриптора !! Имея это в виду, я не могу понять, почему B.v = 3
слепо перезаписывает, v
а не срабатывает v.__set__
.
Отказ от ответственности 1: Я не перестраивал Python из исходного кода с помощью printfs, поэтому я не совсем уверен, что type_setattro
вызывается во время B.v = 3
.
Отказ от ответственности 2: VocalDescriptor
не предназначен для иллюстрации определения «типичного» или «рекомендуемого» дескриптора. Это многословный запрет, чтобы сказать мне, когда вызывается метод.
источник
__get__
, почему__set__
вообще сработало , а не почему .__get__
метода.B.v = 3
эффективно переписал атрибут сint
.__get__
будет ли вызываться, и реализации по умолчаниюobject.__getattribute__
иtype.__getattribute__
вызывать__get__
при использовании экземпляра или класса. Назначение через__set__
только для экземпляра.__get__
методы дескрипторов должны запускаться при вызове из самого класса. Вот как реализованы @classmethods и @staticmethods в соответствии с руководством . @ Jab Мне интересно, почемуB.v = 3
может переписать дескриптор класса. Основываясь на реализации CPython, я ожидал,B.v = 3
что также сработает__set__
.Ответы:
Вы правы, что
B.v = 3
просто перезаписывает дескриптор целым числом (как и должно быть).Для
B.v = 3
вызвать дескриптор, дескриптор должны был быть определен на метаклассе, т.е. наtype(B)
.Чтобы вызвать дескриптор
B
, вы должны использовать экземпляр:B().v = 3
будет делать это.Причина
B.v
вызова getter состоит в том, чтобы разрешить возврат самого экземпляра дескриптора. Обычно вы делаете это, чтобы разрешить доступ к дескриптору через объект класса:Теперь
B.v
вернул бы некоторый экземпляр, с<mymodule.VocalDescriptor object at 0xdeadbeef>
которым вы можете взаимодействовать. Это буквально объект дескриптора, определенный как атрибут класса, и его состояниеB.v.__dict__
используется всеми экземплярами классаB
.Конечно, пользовательский код должен точно определить, что он хочет
B.v
делать, возвращениеself
- это просто общий шаблон.источник
__get__
он предназначен для вызова в качестве атрибута экземпляра или атрибута класса, но__set__
предназначен для вызова только в качестве атрибута экземпляра. И соответствующие документы: docs.python.org/3/reference/datamodel.html#object.__get___PyObject_GenericSetAttrWithDict
он извлекает Py_TYPE из B какtp
, который является метаклассом B (type
в моем случае), затем это метакласс,tp
который обрабатывается логикой короткого замыкания дескриптора . Таким образом, дескриптор, определенный непосредственно дляB
, не виден этой логикой короткого замыкания (поэтому в моем исходном коде__set__
это не называется), но дескриптор, определенный для метакласса , виден логикой короткого замыкания.__set__
метод этого дескриптора является называются.Запрет любых переопределений,
B.v
эквивалентенtype.__getattribute__(B, "v")
, в то времяb = B(); b.v
как эквивалентенobject.__getattribute__(b, "v")
. Оба определения вызывают__get__
метод результата, если он определен.Обратите внимание, думал, что призыв к
__get__
каждому отличается.B.v
передаетNone
в качестве первого аргумента, аB().v
передает сам экземпляр. В обоих случаяхB
передается второй аргумент.B.v = 3
, с другой стороны, эквивалентно томуtype.__setattr__(B, "v", 3)
, что не вызывает__set__
.источник