Python中的不可变对象,可以有弱引用

时间:2017-11-04 22:26:10

标签: python python-2.7 oop tuples

我一直在继承tuple或使用namedtuple幸福几年,但现在我有一个用例,我需要一个可以用作弱指示对象的类。今天我学会了tuples don't support weak references

是否有另一种方法可以在Python中使用一组固定的属性创建不可变对象?我不需要元组的数字索引或可变宽度。

class SimpleThingWithMethods(object):
    def __init__(self, n, x):
        # I just need to store n and x as read-only attributes 
    ... ??? ...

我想这引起了一个明显的问题:为什么不可改变; “Pythonic”代码通常只是假设我们都是这里的成年人,如果它有可能破坏类不变量,那么没有一个心智正常的人会进入一个阶级并且使用它的值。在我的情况下,我在库中有一个类,我担心最终用户意外修改对象。我工作的人有时会对我的代码做出不正确的假设并开始做我没想到的事情,所以如果我不小心修改我的代码就会引发错误,那就更清晰了。

我并不担心防弹不变;如果某个人真的是邪恶的想要修改的东西,那么,好吧,他们是独立的。我只是想让我不小心修改我的对象。

3 个答案:

答案 0 :(得分:4)

嗯,这不是一个很好的答案,但看起来我可以修改https://stackoverflow.com/a/4828492/44330中的答案---基本上覆盖__setattr____delattr__以满足我的需求反对意外修改。 (但不如子类化tuple

那么好
class Point(object):
    __slots__ = ('x','y','__weakref__')
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __hash__(self):
        return self.x.__hash__() * 31 + self.y.__hash__()

实施@Elazar的想法:

class Point(object):
    __slots__ = ('x','y','__weakref__')
    def __new__(cls, x, y):
        thing = object.__new__(cls) 
        object.__setattr__(thing, "x", x)
        object.__setattr__(thing, "y", y)
        return thing
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __hash__(self):
        return self.x.__hash__() * 31 + self.y.__hash__()    

答案 1 :(得分:2)

如果您不担心isinstance检查,可以加强回答:

def Point(x, y):
    class Point(object):
        __slots__ = ('x','y','__weakref__')
        def __setattr__(self, *args):
            raise TypeError
        def __delattr__(self, *args):
            raise TypeError
        def __eq__(self, other):
            return x == other.x and y == other.y
        def __hash__(self):
            return x.__hash__() * 31 + y.__hash__()
    p = Point()
    object.__setattr__(p, "x", x)
    object.__setattr__(p, "y", y)
    return p

我不推荐它(每次调用都会创建一个类!),只是想注意这种可能性。

也可以一直使用javascript,并提供将访问局部变量的__getattr__。但除了创造之外,这也将减缓访问速度。现在我们根本不需要这些插槽:

class MetaImmutable:
    def __setattr__(self, name, val):
        raise TypeError

def Point(x, y):
    class Point(object):
        __metaclass__ = MetaImmutable
        __slots__ = ('__weakref__',)
        def __getattr__(self, name):
            if name == 'x': return x
            if name == 'y': return y
            raise TypeError
        @property
        def x(self): return x
        @property
        def y(self): return y
        def __eq__(self, other):
            return x == other.x and y == other.y
        def __hash__(self):
            return x.__hash__() * 31 + y.__hash__()
    return Point()

测试它:

>>> p = Point(1, 2)
>>> p.y
2
>>> p.z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __getattr__
TypeError
>>> p.z = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> object.__setattr__(p, 'z', 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> from weakref import ref
>>> ref(p)().x
1
>>> type(p).x = property(lambda self: 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __setattr__
TypeError

最后,你仍然可以打破它:

>>> type.__setattr__(type(p), 'x', property(lambda self: 5))
>>> p.x
5

同样,这里没有任何建议。使用@Jasons实现。

答案 2 :(得分:1)

如何在参数(getter?)上使用封装和抽象:

set KnightMoves := {(i,j) in 
  ({-1,1} cross {-2,2})
  union 
  ({-2,2} cross {-1,1})
  }; # this defines the set of moves {(-1,-2),(-1,2), ... (2,1)}

subject to attack_each_cell {i in 1..n,j in 1..n}: 
  P[i,j] + sum{i1 in 1..n, j1 in 1..n: (i-i1,j-j1) in KnightMoves} P[i1,j1]
>= 1;

=&GT; 3