Я играл с дескрипторами и в итоге уперся в стену. Я думал, что смогу напрямую вызывать его, как и любые другие методы, но, видимо, это не кажется последовательным, или я что-то упускаю.
Скажем, у меня есть класс координат, который служит дескриптором:
class Coordinate:
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
return instance.__dict__[self._name]
def __set__(self, instance, value):
try:
instance.__dict__[self._name] = float(value)
except ValueError:
raise ValueError(f'"{self.__set_name__}" must be a number') from None
И класс Point, который будет иметь два свойства — координаты:
class Point:
x = Coordinate()
y = Coordinate()
def __init__(self, x, y):
self.x = x
self.y = y
Допустим, я создаю новую точку в своем основном коде:
point1 = Point(12,5)
Я знаю, что если я попытаюсь это сделать:
print(point1.x)
Я получу это в консоли: 12.0
Я знаю, что если я попробую это, это потерпит неудачу:
print(point1.x.__get__(Point))
Мой метод get будет вызываться со следующими параметрами:
Все это из-за порядка аргументов. Поскольку у меня есть:
x.__get__()
x будет моим первым аргументом.
Тогда, поскольку у меня есть point1.x.__get()
x будет моим первым аргументом, а point1 будет моим вторым аргументом.
И если класс Point, переданный внутри скобок, завершит то, что я хотел, но это все равно не сработает, потому что, хотя метод get возвращает 12.0
, Python теперь попытается вызвать метод get в float (12.0), возвращенный моим руководством get вызов, и это вызовет исключение.
'float' object has no attribute '__get__'
Все это имеет для меня смысл. Что не имеет смысла, так это то, что когда я пытаюсь сделать что-то вроде этого:
print(Point.x.__get__(Point))
Вместо заполнения аргументов метода get следующим образом:
Он заполняет аргумент экземпляра как None.
Почему это происходит? Почему он не заполняется ссылкой на Point, если все остальные аргументы передаются правильно? (Оба аргументы self и Owner имеют ожидаемые значения).
Когда вы это сделаете:
Point.x.__get__(Point)
Он выдает ошибку еще до того, как событие достигнет части .__get__(Point)
, потому что Point.x
вызывает ваш дескриптор с помощью instance=None
, именно так, как и следовало ожидать, поскольку дескриптор был вызван в классе, а не в каком-либо экземпляре.
Вы пытаетесь сделать странную комбинацию вызова протокола дескриптора, не вызывая его. Это могло бы быть возможно, если бы вы сделали что-то вроде if instance is None: return self
, что похоже на то, что делает встроенный дескриптор свойства, но, по сути, я бы посоветовал вам пересмотреть все, что вы здесь пытаетесь сделать. Если вам просто нужен объект дескриптора, лучший способ добраться до него:
vars(Point)['x'] # equivalent to Point.__dict__['x']
Спасибо за ответ! На самом деле я не хочу использовать его для реального кода, я просто экспериментирую с ним, чтобы получить более глубокое понимание. Я действительно выяснил, почему у меня такое поведение: когда я вызываю Point.x.__get(Point), как вы сказали, я фактически вызываю свой дескриптор дважды. В первый раз он передает экземпляр как None, поскольку я вызываю его в классе, а затем использую эти значения для вызова метода get СНОВА, но теперь он использует мой экземпляр как None.
@RicardoIanelli нет, второго явного вызова .__get__
никогда не происходит! Ошибка предотвращает это
Вообще-то ты прав, @juanpa.arrivillaga! Теперь я понял: думаю, вся хитрость в том, чтобы не думать слишком много рано утром или в конце дня. 😂 Спасибо вам огромное!
При запуске вашего кода я получаю следующую ошибку:
Traceback (most recent call last):
File "descriptor_test.py", line 52, in <module>
print(_Point.x.__get__())
File "descriptor_test.py", line 6, in __get__
return instance.__dict__[self._name]
AttributeError: 'NoneType' object has no attribute '__dict__'. Did you mean: '__dir__'?
Это должно быть в значительной степени самоочевидно. Проблема в том, что передается параметр instance
None
, что должно быть нормально, если он вам не нужен в вашем методе __set__()
. К сожалению, ваш метод __set__
использует параметр instance
, поэтому он не работает.
«self: будет point1.x, экземпляр Координата». Мне кажется, у вас тут какое-то недопонимание.
point1.x
— это число с плавающей запятой, поэтому вы пытаетесь вызватьfloat.__get__
, которого не существует.