Reputation: 801
I am coming up against some unexpected behavior with this code:
from pprint import pprint
class a (object):
x = ['1']
class b (a):
x = a.x
x.append('2')
class c (a):
x = a.x
x.append('3')
class d (a):
x = a.x
x = 'nothing'
if __name__ == '__main__':
pprint(a.x)
pprint(b.x)
pprint(c.x)
pprint(d.x)
I receive the output:
['1', '2', '3']
['1', '2', '3']
['1', '2', '3']
'nothing'
But I would expect to receive:
['1']
['1', '2']
['1', '3']
'nothing'
What I don't understand is:
Upvotes: 1
Views: 95
Reputation: 881553
This is probably one of the hardest things for new Python developers to understand and it becomes a real epiphany when they finally do. What you have there is no different to:
>>> x = [1]; print(x)
[1]
>>> y = x; print(x); print(y)
[1]
[1]
>>> y.append(2); print(x); print(y)
[1, 2]
[1, 2]
>>> z = x; z = 'nothing'; print(x); print(y); print(z)
[1, 2]
[1, 2]
nothing
The reason is does this is because, in Python, everything is an object and variables are simply bindings to objects.
So x = [1]
creates the object [1]
and binds the x
variable to it.
When you then do y = x
, that doesn't create a new object, it simply binds y
to the already-existing object. That means x
and y
are now both bound to the same object so, when you do an operation that modifies the object, like x.append()
, both x
and y
will be affected.
That doesn't occur with z = x; z = 'nothing'
since the second step there is creating a new object and binding z
to it, so that x/y
and z
are now bound to different things.
It also doesn't occur with either of:
z = x[:]
z = [item for item in x]
since both of those also create a new object (a copy of the original(1)), separating x
and z
.
The effect can be seen by examining the IDs of each variable (probably the only reason you should ever use id
), with common prefix removed for readability:
>>> print(id(x)); print(id(y)); print(id(z))
49864
49864
53808
You can see that x
and y
are the same object, and z
is different.
(1) Keep in mind you may need to do a deep copy if your data structures are sufficiently complex, such as (with comments added):
>>> x = [[1,2],[3,4]]; y = x; print(id(x)); print(id(y))
773190920 # same object
773190920
>>> y = x[:]; print(id(x)); print(id(y))
773190920 # different objects
832649864
>>> print(id(x[0])); print(id(y[0]))
773088840 # but the sub-objects are still identical.
773088840
Upvotes: 0
Reputation: 116
When you define it this way. The variable x is initialised with the parent class and each child that inherits the class takes a reference to the variable (not a copy). It becomes a global variable under the class name.
To achieve the output you expected:
from pprint import pprint
class a (object):
def __init__(self):
self.x = ['1']
class b (a):
def __init__(self):
super().__init__()
self.x.append('2')
class c (a):
def __init__(self):
super().__init__()
self.x.append('3')
class d (a):
def __init__(self):
super().__init__()
self.x = 'nothing'
if __name__ == '__main__':
pprint(a().x)
pprint(b().x)
pprint(c().x)
pprint(d().x)
Upvotes: 1
Reputation: 54148
1. 2. When doing x = a.x
you're just assigning x
to point the to the only existing list, the one from a
so when operating on x
this reflects on a.x
because this is the same list, not a copy. This is both correct for class b
and class c
. To do a copy
x = list(a.x)
3. When doing x='nothing'
you're assigning a string into x
which doesn't point anymore to the list, just that
Upvotes: 2
Reputation: 76297
Your line
class b (a):
x = a.x
creates another "name" for a.x
, namely x
(in that scope), but they are the same object. If you append to x
, you are also appending to a.x
- it is appending to the same object.
The only place you do something different is in
x = 'nothing'
where you are now binding x
to a different object, a string.
If you would change your code to
class b (a):
x = a.x.copy()
you would get different behaviour: that says that x
is now a "name" for a copy of the list that is a.x
.
Upvotes: 2