Reputation: 669
How do you update a self.dictionary or self.list that contains other self.variables after being changed outside?
For instance we have the following test class:
class Test():
def __init__(self):
self.some_number = 0
self.other_number = 0
self.lst=[self.some_number, self.other_number]
self.dic={'some_number': self.some_number, 'other_number': self.other_number}
def update_values(self):
self.number = 2
self.other_number = 3
Then updating the values:
test = Test()
print(test.dic)
print(test.lst)
test.update_values()
print(test.dic)
print(test.lst)
results in no change:
{'other_number': 0, 'some_number': 0}
[0, 0]
{'other_number': 0, 'some_number': 0}
[0, 0]
I actually thought, that the dictionary and list hold the reference to these variables pointing somewhere in memory. So if these change, that should change as well. But this doesn't seem to be the case. Is there any explanation?
Upvotes: 2
Views: 4337
Reputation: 55479
Integers are immutable, so that won't work. But you can achieve what you want using lists:
class Test(object):
def __init__(self):
self.some_number = [0]
self.other_number = [0]
self.lst=[self.some_number, self.other_number]
self.dic={'some_number': self.some_number,
'other_number': self.other_number}
def update_values(self):
self.some_number[0] = 2
self.other_number[0] = 3
test = Test()
print(test.dic)
print(test.lst)
test.update_values()
print(test.dic)
print(test.lst)
output
{'some_number': [0], 'other_number': [0]}
[[0], [0]]
{'some_number': [2], 'other_number': [3]}
[[2], [3]]
Note that we have to mutate the some_number
and other_number
lists, i.e., modify their contents. If we simply replace them with new lists, then we don't get the desired propagation:
#This doesn't do what we want because it replaces the old list
#objects with new ones.
def update_values(self):
self.some_number = [2]
self.other_number = [3]
The way Python name binding works can be a little bewildering at first, especially if you're coming from another language. IMO, it's best to try to understand this idiom in its own terms, rather than trying to understand it in terms of references, pointers, etc.
I suggest you take a look at Facts and myths about Python names and values by SO veteran Ned Batchelder; that article's also available in video form on YouTube.
In a comment you ask:
If python dynamically rebuilds the list / dic, what happens to the old data? Will Python copy from memory to memory? Or just reassign?
A Python variable, whether it's a simple variable or an attribute like self.some_number
, isn't a container that holds a value. It's a name that's been bound to an object. a=0
creates an int
object with a value of 0 and binds that object to the name a
in the local scope. If you then do a=2
an int
object with a value of 2 is created and bound to a
, and the previous 0 object is reclaimed.
(For efficiency, small integers in the range -5 and 256 (inclusive) are cached, integer objects outside that range are created when needed and destroyed when they're no longer needed, i.e., when there are no longer any names bound to them).
When you do
a = 1234
b = 5678
mylist = [a, b]
Two integer objects are created and bound to the names a
and b
. IOW, a
is a name for the 1234
object, b
is a name for the 5678
object. Sometimes this is described by saying that a
holds a reference to the 1234
integer object. But IMO it's better (and simpler) to think of a
as being a key in a dictionary, with the 1234
integer object as the associated value.
Then mylist = [a, b]
makes mylist[0]
an alternate name for the 1234
object, and mylist[1]
an alternate name for the 5678
object. The 1234
and 5678
objects themselves aren't copied in memory. Essentially, a Python list is an array of pointers to the objects it holds (with some added machinery that makes the list a proper Python object).
In mkrieger1's code, a new list object is created every time you retrieve the Test.lst
property; there's no "old list" unless you've saved it somewhere by binding it, somehow.
I hope that was helpful and not too confusing. :)
Upvotes: 2
Reputation: 23139
If you want to reflect the updated values in what you get when using self.lst
or self.dic
, make them a property
:
class Test():
def __init__(self):
self.some_number = 0
self.other_number = 0
def update_values(self):
self.some_number = 2
self.other_number = 3
@property
def lst(self):
return [self.some_number, self.other_number]
@property
def dic(self):
return {'some_number': self.some_number,
'other_number': self.other_number}
>>> t = Test()
>>> t.lst
[0, 0]
>>> t.dic
{'some_number': 0, 'other_number': 0}
>>> t.update_values()
>>> t.lst
[2, 3]
>>> t.dic
{'some_number': 2, 'other_number': 3}
Upvotes: 5
Reputation: 599610
Yes, names are references. But when in update_values
you do self.number = whatever
, you are rebinding self.number to point to a different integer. self.lst
still contains a reference to the original integer.
If integers were mutable, you could mutate the value of self.number
and that would be reflected in self.lst
. But they aren't, so you can't. If you used some other kind of object, that would be visible though. For example:
def __init__(self):
self.first = [0]
self.lst = [self.first]
def update_values(self):
self.first[0] += 1
Here we are mutating self.first, not rebinding it, so self.lst will reflect the change.
Upvotes: 3