强迫对象不变

时间:2010-11-16 23:50:30

标签: python

在开始之前,我已经意识到Python中的对象不变性通常是一个坏主意,但是,我相信在我的情况下它是合适的。

假设我正在使用我的代码中的坐标系,这样每个坐标都使用X,Y,Z的结构。我已经重载减法,加法等方法来做我想要的。我当前的问题是赋值运算符,我读过它不能重载。问题是当我有以下内容时,我不希望A指向与B相同的点,我希望两者是独立的,以防我需要覆盖一个坐标而不是另一个坐标:

B = Point(1,2,3)
A = B

我知道我可以使用深度复制,但这看起来像是一个黑客,特别是因为我可以有一个点列表,我可能需要采取一片(在这种情况下,它将再次有一点点参考,而不是点)。我也考虑使用元组,但我的观点有我需要的成员方法,而且我的代码的很大一部分已经使用了结构。

我的想法是将Point修改为不可变的,因为它实际上只有3个浮点数据,并且通过做一些研究_new _()似乎是为此覆盖的正确函数。我不知道如何实现这一目标,它会是这样的还是我离开的?

def __new__(self):
    return Point(self.x, self.y, self.z)

编辑: 我的不好,我在阅读katrielalex的帖子后意识到,一旦定义了不可变对象的参数,我就无法修改它,在这种情况下,A和B都指向相同的数据不是问题,因为重新分配需要创建一个新的观点。我会说katrielalex和vonPetrushev的帖子实现我想要的,我想我会选择vonPetrushev的解决方案,因为我不需要重写所有当前代码来使用元组(额外的括号集而不能引用坐标为point.x)

3 个答案:

答案 0 :(得分:4)

结合katrielalex的建议,将Point命名为元组也会很好。在这里,我刚刚将tuple父级替换为namedtuple('Point', 'x y z') - 这足以让它发挥作用。

>>> from collections import namedtuple
>>> class Point(namedtuple('Point', 'x y z')):
...     def __add__(self, other):
...             return Point((i + j for i, j in zip(self, other)))
...
...     def __mul__(self, other):
...             return sum(i * j for i, j in zip(self, other))
...
...     def __sub__(self, other):
...             return Point((i - j for i, j in zip(self, other)))
...
...     @property
...     def mod(self):
...             from math import sqrt
...             return sqrt(sum(i*i for i in self))
...

然后你可以:

>>> Point(1, 2, 3)
Point(x=1, y=2, z=3)
>>> Point(x=1, y=2, z=3).mod
3.7416573867739413
>>> Point(x=1, y=2, z=3) * Point(0, 0, 1)
3
>>> Point._make((1, 2, 3))
Point(x=1, y=2, z=3)

(感谢katrielalex建议扩展namedtuple而不是复制生成的代码。)

答案 1 :(得分:1)

你可以使Point成为tuple的子类 - 记住,内置类型(至少在最近的Pythons中)只是更多的类。这将为您提供所需的不变性。

但是,我对你建议的用例感到有些困惑:

  

如果我需要覆盖一个坐标而不是另一个坐标:

如果Point是不可变的,那就没有意义......


>>> class Point(tuple):
...     def __add__(self, other):
...             return Point((i + j for i, j in zip(self, other)))
...
...     def __mul__(self, other):
...             return sum(i * j for i, j in zip(self, other))
...
...     def __sub__(self, other):
...             return Point((i - j for i, j in zip(self, other)))
...
...     @property
...     def mod(self):
...             from math import sqrt
...             return sqrt(sum(i*i for i in self))
...
>>> a = Point((1,2,3))
>>> b = Point((4,5,6))
>>> a + b
(5, 7, 9)
>>> b - a
(3, 3, 3)
>>> a * b
32
>>> a.mod
3.7416573867739413
>>> a[0] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Point' object does not support item assignment

答案 2 :(得分:0)

试试这个:

class Point(object):
    def __init__(self, x, y, z):
        self._x=x
        self._y=y
        self._z=z

    def __getattr__(self, key):
        try:
            key={'x':'_x','y':'_y','z':'_z'}[key]
        except KeyError:
            raise AttributeError
        else:
            return self.__dict__[key]

    def __setattr__(self, key, value):
        if key in ['_x','_y','_z']:
            object.__setattr__(self, key, value)
        else:
            raise TypeError("'Point' object does not support item assignment")

因此,您可以构造一个Point对象,但不能更改其属性。