first = 30
second = first
first = 20
print("first: ", first, "second: ", second)
# first: 20 second: 30
In the above example, it seems the variable second points directly to the memory address for 30, and when first is reassigned it therefore has no effect on second.
class Circle:
pi = 3.1419
circle1 = Circle()
circle2 = Circle()
circle2.pi = 10
print(Circle.pi, circle1.pi, circle2.pi)
# 3.1419 3.1419 10
Circle.pi = 40
print(Circle.pi, circle1.pi, circle2.pi)
# 40 40 10
In the above example however, when I assign 40 to Circle.pi, it has an affect on circle1.pi because circle1.pi was pointing to Circle.pi instead of the memory address for 3.1419.
circle1.pidoesn't point toCircle.pi, it isCircle.pi. As written,circle1has no attributepi, so when you attempt to accesscircle1.pi, the lookup defers to the class object. If you write e.g.circle1.pi = 7you will add an attribute tocircle1, but you will not mutateCircleitself.circle1.piwhencircle1has no attributepi(i.e.circle1.__dict__doesn't contain an entry forpi) is translated to a lookup on the class object (i.e.Circle.pi). Essentially, writingcircle1.piresults incircle1.__dict__being checked, and if that fails,circle1.__class__.__dict__is checked. If neither containpi, anAttributeErroris raised.