使对象相对于其父对象是不可变的

时间:2019-02-28 09:47:31

标签: python immutability

背景

如果用户定义的对象具有不可变的对象作为属性,则在属性上使用+=运算符与在指向该属性的另一个变量上使用它具有不同的效果:

class A():
    def __init__(self):
        # Set self.x to be an (immutable) int
        self.x = 5

a = A()

现在a.x == 5

a.x += 1

现在a.x指向另一个对象,并且a.x == 6指向

x = a.x

现在xa.x指向同一对象:id(x) == id(a.x) and x == a.x == 6

x += 1

现在xa.x指向不同的对象id(x) != id(a.x) and x == 7 and a.x == 6

我的问题

是否可以实现具有属性B的类a,以使+=运算符在b.a.x上以相同的方式工作?即 我想要以下行为

要求1

b = B()

现在我要b.a.x == 5

要求2

b.a.x += 1

现在我要b.a.x == 6

要求3

a = b.a
a.x += 1

现在,我想要b.a.x == 6。我希望增加a.x以影响b.a.x

要求4

a = A()
b.a = a

现在我要b.a.x == 5

要求5

x = b.a.x
a = b.a

现在我要x == 5 and a.x == 5 and b.a.x == 5

要求6

x += 1

现在,我想要x == 6 and a.x == 5 and b.a.x == 5。我不想增加x来影响a.xb.a.x

换句话说,我希望能够使用+=运算符来影响b.a.x,但是将 only 直接应用于{{1} },而不是在操作时将其应用于绑定到同一对象的另一个名称时。

我尝试过的

B类的简单定义不起作用:

b.a.x

此要求不满足3。

但是,如果我将class B(): def __init__: self.a = A() 更改为一个返回b.a的新副本的属性,则这也不起作用:

a

此要求未通过2。

我还尝试将X实现为返回新类实例的new class with an __iadd__ method。这意味着class B() def __init__: self._a = A() @property def a(self): return copy(_a) @a.setter def a(self, value) self._a = value 在应用x时仍然表现为不变,但是我无法弄清楚如何同时满足上述2和3要求。

我正在使用Python 3。

2 个答案:

答案 0 :(得分:1)

期望的行为完全取决于+= 类型的x运算符的实现方式。

例如,如果x指向列表对象,则不会得到所需的行为,如果x指向int对象,则会得到该行为。 / p>

因此,您的类A或类B确实无法执行任何操作或需要执行任何操作来获得这种行为。

无论您采取什么措施来获得此行为,都必须以x

类型完成

答案 1 :(得分:0)

TL; DR您所要提出的要求是不可能的,并且违反了语言本身的许多承诺。

考虑要求3
您创建了一个B的新实例B()(它在其构造函数中创建了A的实例),引用b指向该实例。

b.a是另一个参考,指向已经创建的A实例。
这段代码x = b.a只会创建一个指向A的相同实例的引用。对该对象所做的任何更改都将通过指向该对象的两个引用可见。
这是有道理的,因为这个A实例不知道(也不在乎)它是否是另一个实例的成员。在两种情况下,其行为都应相同。 (这是python语言的承诺,无论从何处引用该对象,其行为都相同。)