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



Background

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

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

我的问题

是否可以实现具有属性a的类B,以便+=运算符在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,但当它直接应用于b.a.x时,而不是当它应用于在操作时绑定到同一对象的另一个名称时。

我尝试过什么

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

class B():
def __init__:
self.a = A()

这不符合要求 3。

但是,如果我b.a更改为返回a新副本的属性,那么这也不起作用:

class B()
def __init__:
self._a = A()
@property
def a(self):
return copy(_a)
@a.setter
def a(self, value)
self._a = value

这不符合要求 2。

我还尝试使用返回新类实例的__iadd__方法将 X 实现为新类。这意味着x在应用+=时仍然表现得好像它是不可变的,但我无法弄清楚如何同时满足上面的要求 2 和 3。

我正在使用Python 3。

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

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

因此,实际上,您不能或不需要与类A或类B执行任何操作来获得此行为。

无论你做什么来获得这种行为,都必须在x

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

考虑要求 3
您创建了 BB()的新实例(在其构造函数中创建 A 的实例)和指向此实例的引用b

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

最新更新