Внутреннее устройство метода __get__ в Python

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

Скажем, у меня есть класс координат, который служит дескриптором:

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 будет вызываться со следующими параметрами:

  • self: будет point1.x, экземпляр Координата.
  • экземпляр: будет моей точкой1, экземпляром Point.
  • владелец: Будет класс Point.

Все это из-за порядка аргументов. Поскольку у меня есть: 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 следующим образом:

  • self: будет Point.x, экземпляр координаты, объявленный в классе Point.
  • экземпляр: будет ссылкой на сам мой класс Point.
  • владелец: Также будет ссылкой на сам класс Point.

Он заполняет аргумент экземпляра как None.

Почему это происходит? Почему он не заполняется ссылкой на Point, если все остальные аргументы передаются правильно? (Оба аргументы self и Owner имеют ожидаемые значения).

«self: будет point1.x, экземпляр Координата». Мне кажется, у вас тут какое-то недопонимание. point1.x — это число с плавающей запятой, поэтому вы пытаетесь вызвать float.__get__, которого не существует.

wim 25.07.2024 02:26
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда вы это сделаете:

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.

Ricardo Ianelli 25.07.2024 18:50

@RicardoIanelli нет, второго явного вызова .__get__ никогда не происходит! Ошибка предотвращает это

juanpa.arrivillaga 26.07.2024 03:00

Вообще-то ты прав, @juanpa.arrivillaga! Теперь я понял: думаю, вся хитрость в том, чтобы не думать слишком много рано утром или в конце дня. 😂 Спасибо вам огромное!

Ricardo Ianelli 26.07.2024 21:07

При запуске вашего кода я получаю следующую ошибку:

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__'?

Это должно быть в значительной степени самоочевидно. Проблема в том, что передается параметр instanceNone, что должно быть нормально, если он вам не нужен в вашем методе __set__(). К сожалению, ваш метод __set__ использует параметр instance, поэтому он не работает.

Другие вопросы по теме