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.