Проблема в том, что вы смешиваете некоторые концепции, пытаясь использовать Python property
, и все становится запутанным -
Суть в том, что если нужно вычислить pixel_position
- этосам по себе должен иметь свойство.
То, что вы пытаетесь сделать, - это установить некоторые параметры для установки значений в "block_position" и получить - когда block_position изменилось, новое значение для pixel_position.Это не работает, потому что вы не «схватили» все способы, которыми можно было бы изменить значения внутри вашего block_position.
Что происходит, когда вы делаете:
block.block_position.x += 1
Является ли свойство getter
для block_position активированным - у объекта Point в этом случае его атрибут x
будет изменен - но это изменениеникогда не проходит через внешний блок-объект, так как x
является обычным атрибутом, а не свойством.
Теперь можно оборудовать ваш класс Point
, чтобы действия моглисрабатывать при каждом изменении x
ou y
- но это может стать действительно сложным и быстрым.
Лучшим подходом является то, чтобы сам pixel_position был свойством вместо обычного атрибута, а его значения лениво вычислялись - генерировались всякий раз, когда они необходимы - и не зависели от значения, которое должно быть установлено с нетерпением всякий раз, когда block_positionизменения.Это паттерн «реактивного программирования».На самом деле вы обнаружите, что само «block_poisition» может быть обычным атрибутом экземпляра, а не свойством - если только вы не хотите, чтобы его средство проверки гарантировало, что назначенный объект является экземпляром Point
.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scale):
return Point(self.x * scale, self.y * scale)
class Block:
size = 40
def __init__(self, x=1,y=1):
self._block_position = Point(x,y)
@property
def block_position(self):
return self._block_position
@block_position.setter
def block_position(self, point):
if not isinstance(point, Point):
# raise TypeError() # or, as an option, accept sequences of 2 coordinates:
point = Point(point[0], point[1])
self._block_position = point
@property
@pixel_position(self):
return self._block_position * self.size
Итак, теперь все наоборот - и pixel_position не может быть установлен и гарантированно будет всегда обновляться.
Три вещи там:
- Я добавил
__mul__
метод для вашей точки, так что теперь можно просто использовать оператор *
, чтобы умножить его на скаляр. - Нет необходимости или смысла в добавлении
__
к «скрытым» атрибутам Python,Ранние учебники или неофициальная документация по языку могут ввести в заблуждение, говоря, что это способ иметь «личные атрибуты».Это ошибка - в Python нет закрытых атрибутов, которые __
делает для искажения имен, чтобы позволить классу иметь атрибуты, которые не перепутаны подклассами.За почти 20 лет программирования на Python мне никогда не требовалась эта функция.С другой стороны, он может выдавать странные ошибки, если у вас есть подклассы Block
.Просто не надо.Общепринятым соглашением является то, что один «_» указывает на атрибут, который не должен изменяться или использоваться пользователями класса напрямую, и это не имеет побочных эффектов. - Без установщика
pixel_position
в основном «неизменяемый»"- и если кто-то изменит атрибуты внутри него после получения block.pixel_position
, он будет изменять отсоединенный экземпляр Point.
Если вам действительно необходимо переключение между пикселями_позицией и блок-позицией в обоих направлениях (то есть сделать так, чтобы пользователь класса мог изменить один из атрибутов и отразить изменение в другом), а не пытатьсячтобы использовать уведомление об изменении внутри Point
, я предлагаю вместо этого сделать Point
неизменным классом.Любой, кто хочет изменить координаты, должен будет создать новый экземпляр точки - в результате block.block_position.x += 1
больше не будет работать - нужно будет сделать: block.block_position += Point(1, 0)
(и тогда вы реализуете __add__
в Point, как я делал __mul__
).Тогда вы можете написать свой установщик для pixel_position
, чтобы принудительно установить новое значение в block_position
, если оно будет изменено.
С другой стороны, если вы сделаете Point неизменяемым, вы можете добавить к нему __hash__
и заставить его работать в наборах и в качестве ключей словаря: открывается множество других применений.
Проверьте реализацию class V2
в этого проекта , чтобы иметь представление о хорошей реализации.(Отказ от ответственности: над проектом ссылок я сейчас работаю как хобби, и на самом деле я реализовал этот класс за последнюю неделю)