首页 > 解决方案 > 字段的字段的设置器

问题描述

给定代码:

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)

但是如果我想增加x...block position好吧,代码不会进入 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。

标签: pythongetter-setter

解决方案


那里的问题是你正在混合一些试图利用 Python 的概念property并且事情变得混乱 -

基本的事情是,如果pixel_position要计算 - 它本身应该是一个属性。

您正在尝试做的是在“block_position”上设置值并派生一些门 - 当 block_position 更改时,pixel_position 的新值。那是行不通的,因为你没有“关闭”总是有人可能修改你的 block_position 中的值。

当您制作时会发生什么:

 block.block_position.x += 1

是否getter激活了 block_position 的属性 - 那里的 Point 对象然后更改了它的x属性 - 但这种更改永远不会通过外部块对象,因为x它是普通属性,而不是属性。

现在,可以对您的类进行检测,以便在您更改Point时触发操作- 但这可能会变得非常复杂和快速。xy

一个更好的方法是让 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 实例。

如果您确实需要在 pixel_position 和 block_position 之间进行往返更改(也就是说,以这种方式让类用户可以更改任一属性并将更改反映在另一个属性中),而不是尝试在内部设置更改通知 Point,我建议您Point改为创建一个不可变的类。任何想要更改坐标的人都必须创建一个新的点实例 - 结果block.block_position.x += 1将不再工作 - 必须这样做:(block.block_position += Point(1, 0) 然后你__add__在 Point 中实现,就像我做的那样__mul__)。然后你可以编写你的设置器pixel_position来强制一个新值block_position如果它被改变。

从好的方面来说,如果你让 Point 不可变,你可以添加__hash__它并让它在集合中和作为字典键工作:许多其他用途都打开了。

检查这个项目class V2的实现以了解一个很好的实现(免责声明:链接项目是我现在作为爱好工作的东西,过去一周我实际上已经实现了这个类)


推荐阅读