Background
If a user-defined object has an immutable object as an attribute, then using the +=
operator on the attribute has a different effect to using it on another variable pointing to that attribute:
class A():
def __init__(self):
# Set self.x to be an (immutable) int
self.x = 5
a = A()
Now a.x == 5
a.x += 1
Now a.x
points to a different object, and a.x == 6
.
x = a.x
Now x
and a.x
point to the same object: id(x) == id(a.x) and x == a.x == 6
x += 1
Now x
and a.x
point to different objects, id(x) != id(a.x) and x == 7 and a.x == 6
My question
Is it possible to implement a class B
that has an attribute a
such that the +=
operator works in the same way on b.a.x
? i.e. I want the following behaviour:
Requirement 1
b = B()
Now I would like b.a.x == 5
.
Requirement 2
b.a.x += 1
Now I would like b.a.x == 6
.
Requirement 3
a = b.a
a.x += 1
Now I would like b.a.x == 6
. I don't want incrementing a.x
to affect b.a.x
.
Requirement 4
a = A()
b.a = a
Now I would like b.a.x == 5
.
Requirement 5
x = b.a.x
a = b.a
Now I would like x == 5 and a.x == 5 and b.a.x == 5
.
Requirement 6
x += 1
Now I would like x == 6 and a.x == 5 and b.a.x == 5
. I don't want incrementing x
to affect a.x
or b.a.x
.
To put it another way, I want to be able to use the +=
operator to affect b.a.x
, but only when it is applied directly to b.a.x
, not when it is applied to another name that is bound to the same object at the time of the operation.
What I've tried
A simple definition of class B does not work:
class B():
def __init__:
self.a = A()
This fails requirement 3.
However, if I change b.a
to be a property that returns a new copy of a
, then this doesn't work either:
class B()
def __init__:
self._a = A()
@property
def a(self):
return copy(_a)
@a.setter
def a(self, value)
self._a = value
This fails requirement 2.
I also tried implementing X as a new class with an __iadd__
method that returned a new class instance. This meant that x
still acted as if it were immutable when applying +=
, but I couldn't figure out how to achieve both requirements 2 and 3, above.
I'm working in Python 3.