代码之家  ›  专栏  ›  技术社区  ›  Alexander Tsepkov

强制对象不可变

  •  0
  • Alexander Tsepkov  · 技术社区  · 14 年前

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

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

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

    我知道我可以使用deepcopy,但这看起来像是一个技巧,特别是因为我可能有一个点列表,我可能需要从中分一杯羹(在这种情况下,它将再次有一杯羹点引用,而不是点)。我也考虑过使用元组,但是我的点有我需要的成员方法,并且我的代码中有很大一部分已经使用了结构。

    我的想法是将Point修改为不可变的,因为它实际上只有3个浮点数,而且从做一些研究来看,new似乎是覆盖这个的正确函数。不过,我不知道如何实现这一点,是这样的事情还是离我很远?

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

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

    3 回复  |  直到 14 年前
        1
  •  4
  •   Chris Morgan    14 年前

    结合katrielex的建议 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)
    

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

        2
  •  1
  •   Katriel    14 年前

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

    但是,我对您建议的用例有点困惑:

    如果以后需要覆盖其中一个坐标而不是另一个坐标:

    如果 s是不变的。。。


    >>> 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
    
        3
  •  0
  •   vonPetrushev    14 年前

    试试这个:

    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")
    

    因此,可以构造点对象,但不能更改其属性。