Setter 一个字段的字段
Setter for a field of a field
给定代码:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Block:
size = 40
def __init__(self, x=1, y=1):
self.pixel_position = Point(x * Block.size, y * Block.size)
self.__block_position = Point(x, y)
@property
def block_position(self):
return self.__block_position
@block_position.setter
def block_position(self, point):
#I want for pixel_position to be updated whenever block position changes
self.pixel_position.x += point.x * size
self.pixel_position.y += point.y * size
self.__block_position = point
现在对于这样的代码,简单的赋值效果很好
block = Block()
block.block_position = Point(2, 1)
但是如果我想增加 block position
的 x
... 那么,代码不会进入 setter.
block.block_position.x -= 1
# now block_position = (1, 1) but pixel_position = (80, 40) not (40, 40)
我该如何更改它?
我知道我可以通过为 __pixel_position
添加 属性 来解决这个问题,它会在返回自身之前计算 Block.size * __block_position
,但这种方法并不令我满意 - 我想知道如何在 python 中为字段的字段设置 属性。
我的问题不是找到任何解决方案,而是找到一个解决方案,其中更改字段 block_position.x
会将我重定向到我的 setter/getter.
您可以通过将 pixel_position
设为 属性 来解决此问题,因为此属性似乎依赖于另一个 block_position
。这样 block_position
甚至不必是 属性:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'{type(self).__name__}({self.x}, {self.y})'
class Block:
size = 40
def __init__(self, x=1, y=1):
self.block_position = Point(x,y)
@property
def pixel_position(self):
return Point(self.block_position.x * self.size, self.block_position.y * self.size)
block = Block()
block.block_position = Point(2,1)
block.block_position.x -= 1
print(block.block_position) # Point(1, 1)
print(block.pixel_position) # Point(40, 40)
一般来说,依赖属性适用于属性,因为它们可以从其他独立属性计算"on-the-fly"。
问题是您在尝试使用 Python property
时混合了一些概念,事情变得混乱了 -
基本的事情是,如果要计算 pixel_position
- 它本身应该是 属性。
您正在尝试做的是对 "block_position" 的值设置设置一些门控,并导出 - 当 block_position 更改时 pixel_position 的新值。这是行不通的,因为您并不总是 "gatted" 可能会修改 block_position 中的值。
当你做的时候会发生什么:
block.block_position.x += 1
是否激活了 block_position 的 属性 getter
- 那里的 Point 对象然后更改了其 x
属性 - 但此更改永远不会通过外部块对象,因为 x
是普通属性,而不是 属性.
现在,可以检测您的 Point
class,以便在 x
或 [=19] 时触发操作=] 被改变了——但这可能会变得非常复杂,而且速度很快。
一个更好的方法是让 pixel_position 本身是一个 属性,而不是一个普通的属性,并且延迟计算它的值 - 在需要时生成 - 而不是依赖于每当 block_position 更改时立即设置值。这是来自 "reactive programing" 的模式。
实际上,你会发现 "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__
方法添加到您的 Point 中,因此现在可以使用 *
运算符将其乘以标量。
- 实际上在
__
到 Python "hidden" 属性之前没有必要也没有意义。该语言的早期教程或非官方文档可能会误导性地说这是一种拥有 "private attributes" 的方式。这是一个错误 - Python 中没有私有属性 __
所做的是名称重整,以允许 class 具有不被 sub[=95= 搞乱的属性]是的。在将近 20 年的 Python 编程中,我实际上从未需要该功能。另一方面,如果您有 Block
的子 class,它 可以 给您带来奇怪的错误。只是不要。公认的惯例是一个单独的“_”表示 class 的用户不应直接更改或访问的属性,并且这没有副作用。
- 没有 setter,
pixel_position
主要是 "unchangeable" - 如果在检索 block.pixel_position
后确实更改了其中的属性,他将更改一个分离点实例。
如果您确实需要在 pixel_position 和 block_position 之间进行往返更改(也就是说,以这种方式让 class 用户可以更改任一属性并反映更改另一方面),而不是尝试在 Point
中设置更改通知,我建议您将 Point
设为不可变的 class 。任何想要更改坐标的人都必须创建一个新的点实例 - 结果 block.block_position.x += 1
将不再起作用 - 必须这样做:block.block_position += Point(1, 0)
(然后你在 Point 中实现 __add__
,就像我做的那样 __mul__
)。然后,您可以为 pixel_position
编写 setter,以便在更改时将新值强制为 block_position
。
从好的方面来说,如果你让 Point 不可变,你可以向它添加 __hash__
并让它在集合中工作并作为字典键:许多其他用途开放。
检查 class V2
在 this project 上的实现以了解一个好的实现(免责声明:link 项目是我现在作为业余爱好工作的东西,我有实际上在过去一周 class 实施了这个)
鉴于您的要求,我提供了一个示例实现,其中对象的属性在设置时会通知其所有者,以便它与另一个对象同步。我在此示例中只考虑一维点 (x
),因为 y
的实现是完全相同的:
class NotificationProperty(property):
def __init__(self, fget=None, fset=None, fdel=None, doc=None, notify=None):
super().__init__(fget, fset, fdel, doc)
self.notify = notify
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, val):
super().__set__(instance, val)
self.notify(instance, self.name, val)
# Should define similar methods for other attributes
# (see https://docs.python.org/3/howto/descriptor.html#properties).
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__, self.notify)
def notification_property(func):
from functools import partial
return partial(NotificationProperty, notify=func)
class SyncPoint:
def __init__(self, x, sync_with=None):
self.sync_with = sync_with
self.x = x
def sync(self, which, value):
if self.sync_with is not None:
obj, scale = self.sync_with
value = int(scale * value)
if getattr(obj, which) != value: # Check if already synced -> avoid RecursionError.
setattr(obj, which, value)
@notification_property(sync)
def x(self):
return self._x
@x.setter
def x(self, val):
self._x = val
class Block:
size = 40
def __init__(self, x=1):
self.pixel_position = SyncPoint(self.size * x)
self.block_position = SyncPoint(x, sync_with=(self.pixel_position, self.size))
self.pixel_position.sync_with = (self.block_position, 1/self.size)
block = Block(3)
print('block_pos: ', block.block_position.x) # block_pos: 3
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 120
block.block_position.x -= 1
print('block_pos: ', block.block_position.x) # block_pos: 2
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 80
block.pixel_position.x -= Block.size
print('block_pos: ', block.block_position.x) # block_pos: 1
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 40
变化:通过x.setter(func)
指定通知函数
以下是上述代码的变体,让您可以在 x.setter
的定义期间指定要为通知调用的函数。这可能感觉更直观,因为通知发生在 __set__
上,但最终它是一个品味问题:
from functools import partial
class notification_property(property):
def __init__(self, fget=None, fset=None, fdel=None, doc=None, notify=None):
super().__init__(fget, fset, fdel, doc)
self.notify = notify
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, val):
super().__set__(instance, val)
self.notify(instance, self.name, val)
# Should define similar methods for other attributes
# (see https://docs.python.org/3/howto/descriptor.html#properties).
def setter(self, func=None):
return partial(type(self), self.fget, fdel=self.fdel, doc=self.__doc__, notify=(func or self.notify))
class SyncPoint:
def __init__(self, x, sync_with=None):
self.sync_with = sync_with
self.x = x
def sync(self, which, value):
if self.sync_with is not None:
obj, scale = self.sync_with
value = int(scale * value)
if getattr(obj, which) != value: # Check if already synced -> avoid RecursionError.
setattr(obj, which, value)
@notification_property
def x(self):
return self._x
@x.setter(sync)
def x(self, val):
self._x = val
给定代码:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Block:
size = 40
def __init__(self, x=1, y=1):
self.pixel_position = Point(x * Block.size, y * Block.size)
self.__block_position = Point(x, y)
@property
def block_position(self):
return self.__block_position
@block_position.setter
def block_position(self, point):
#I want for pixel_position to be updated whenever block position changes
self.pixel_position.x += point.x * size
self.pixel_position.y += point.y * size
self.__block_position = point
现在对于这样的代码,简单的赋值效果很好
block = Block()
block.block_position = Point(2, 1)
但是如果我想增加 block position
的 x
... 那么,代码不会进入 setter.
block.block_position.x -= 1
# now block_position = (1, 1) but pixel_position = (80, 40) not (40, 40)
我该如何更改它?
我知道我可以通过为 __pixel_position
添加 属性 来解决这个问题,它会在返回自身之前计算 Block.size * __block_position
,但这种方法并不令我满意 - 我想知道如何在 python 中为字段的字段设置 属性。
我的问题不是找到任何解决方案,而是找到一个解决方案,其中更改字段 block_position.x
会将我重定向到我的 setter/getter.
您可以通过将 pixel_position
设为 属性 来解决此问题,因为此属性似乎依赖于另一个 block_position
。这样 block_position
甚至不必是 属性:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'{type(self).__name__}({self.x}, {self.y})'
class Block:
size = 40
def __init__(self, x=1, y=1):
self.block_position = Point(x,y)
@property
def pixel_position(self):
return Point(self.block_position.x * self.size, self.block_position.y * self.size)
block = Block()
block.block_position = Point(2,1)
block.block_position.x -= 1
print(block.block_position) # Point(1, 1)
print(block.pixel_position) # Point(40, 40)
一般来说,依赖属性适用于属性,因为它们可以从其他独立属性计算"on-the-fly"。
问题是您在尝试使用 Python property
时混合了一些概念,事情变得混乱了 -
基本的事情是,如果要计算 pixel_position
- 它本身应该是 属性。
您正在尝试做的是对 "block_position" 的值设置设置一些门控,并导出 - 当 block_position 更改时 pixel_position 的新值。这是行不通的,因为您并不总是 "gatted" 可能会修改 block_position 中的值。
当你做的时候会发生什么:
block.block_position.x += 1
是否激活了 block_position 的 属性 getter
- 那里的 Point 对象然后更改了其 x
属性 - 但此更改永远不会通过外部块对象,因为 x
是普通属性,而不是 属性.
现在,可以检测您的 Point
class,以便在 x
或 [=19] 时触发操作=] 被改变了——但这可能会变得非常复杂,而且速度很快。
一个更好的方法是让 pixel_position 本身是一个 属性,而不是一个普通的属性,并且延迟计算它的值 - 在需要时生成 - 而不是依赖于每当 block_position 更改时立即设置值。这是来自 "reactive programing" 的模式。
实际上,你会发现 "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__
方法添加到您的 Point 中,因此现在可以使用*
运算符将其乘以标量。 - 实际上在
__
到 Python "hidden" 属性之前没有必要也没有意义。该语言的早期教程或非官方文档可能会误导性地说这是一种拥有 "private attributes" 的方式。这是一个错误 - Python 中没有私有属性__
所做的是名称重整,以允许 class 具有不被 sub[=95= 搞乱的属性]是的。在将近 20 年的 Python 编程中,我实际上从未需要该功能。另一方面,如果您有Block
的子 class,它 可以 给您带来奇怪的错误。只是不要。公认的惯例是一个单独的“_”表示 class 的用户不应直接更改或访问的属性,并且这没有副作用。 - 没有 setter,
pixel_position
主要是 "unchangeable" - 如果在检索block.pixel_position
后确实更改了其中的属性,他将更改一个分离点实例。
如果您确实需要在 pixel_position 和 block_position 之间进行往返更改(也就是说,以这种方式让 class 用户可以更改任一属性并反映更改另一方面),而不是尝试在 Point
中设置更改通知,我建议您将 Point
设为不可变的 class 。任何想要更改坐标的人都必须创建一个新的点实例 - 结果 block.block_position.x += 1
将不再起作用 - 必须这样做:block.block_position += Point(1, 0)
(然后你在 Point 中实现 __add__
,就像我做的那样 __mul__
)。然后,您可以为 pixel_position
编写 setter,以便在更改时将新值强制为 block_position
。
从好的方面来说,如果你让 Point 不可变,你可以向它添加 __hash__
并让它在集合中工作并作为字典键:许多其他用途开放。
检查 class V2
在 this project 上的实现以了解一个好的实现(免责声明:link 项目是我现在作为业余爱好工作的东西,我有实际上在过去一周 class 实施了这个)
鉴于您的要求,我提供了一个示例实现,其中对象的属性在设置时会通知其所有者,以便它与另一个对象同步。我在此示例中只考虑一维点 (x
),因为 y
的实现是完全相同的:
class NotificationProperty(property):
def __init__(self, fget=None, fset=None, fdel=None, doc=None, notify=None):
super().__init__(fget, fset, fdel, doc)
self.notify = notify
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, val):
super().__set__(instance, val)
self.notify(instance, self.name, val)
# Should define similar methods for other attributes
# (see https://docs.python.org/3/howto/descriptor.html#properties).
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__, self.notify)
def notification_property(func):
from functools import partial
return partial(NotificationProperty, notify=func)
class SyncPoint:
def __init__(self, x, sync_with=None):
self.sync_with = sync_with
self.x = x
def sync(self, which, value):
if self.sync_with is not None:
obj, scale = self.sync_with
value = int(scale * value)
if getattr(obj, which) != value: # Check if already synced -> avoid RecursionError.
setattr(obj, which, value)
@notification_property(sync)
def x(self):
return self._x
@x.setter
def x(self, val):
self._x = val
class Block:
size = 40
def __init__(self, x=1):
self.pixel_position = SyncPoint(self.size * x)
self.block_position = SyncPoint(x, sync_with=(self.pixel_position, self.size))
self.pixel_position.sync_with = (self.block_position, 1/self.size)
block = Block(3)
print('block_pos: ', block.block_position.x) # block_pos: 3
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 120
block.block_position.x -= 1
print('block_pos: ', block.block_position.x) # block_pos: 2
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 80
block.pixel_position.x -= Block.size
print('block_pos: ', block.block_position.x) # block_pos: 1
print('pixel_pos: ', block.pixel_position.x) # pixel_pos: 40
变化:通过x.setter(func)
指定通知函数
以下是上述代码的变体,让您可以在 x.setter
的定义期间指定要为通知调用的函数。这可能感觉更直观,因为通知发生在 __set__
上,但最终它是一个品味问题:
from functools import partial
class notification_property(property):
def __init__(self, fget=None, fset=None, fdel=None, doc=None, notify=None):
super().__init__(fget, fset, fdel, doc)
self.notify = notify
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, val):
super().__set__(instance, val)
self.notify(instance, self.name, val)
# Should define similar methods for other attributes
# (see https://docs.python.org/3/howto/descriptor.html#properties).
def setter(self, func=None):
return partial(type(self), self.fget, fdel=self.fdel, doc=self.__doc__, notify=(func or self.notify))
class SyncPoint:
def __init__(self, x, sync_with=None):
self.sync_with = sync_with
self.x = x
def sync(self, which, value):
if self.sync_with is not None:
obj, scale = self.sync_with
value = int(scale * value)
if getattr(obj, which) != value: # Check if already synced -> avoid RecursionError.
setattr(obj, which, value)
@notification_property
def x(self):
return self._x
@x.setter(sync)
def x(self, val):
self._x = val