Spotting and fixing problems caused by referencing the same object in memory
Here's a slightly simplified version of the code causing the problem— very slightly simplified; it really was about this short:
def fields(self, key, items):
for (name, cls) in items.items():
for field in dir(cls):
value = getattr(cls, field)
if isinstance(value, Field):
new_name = '__'.join([key, name, field])
value.widget.attrs = {
'data-target-id': name,
}
yield (new_name, value)
It only took me hours to see the problem once isolated to this bit of code. See it?
When modifying the attribute pointed to by the variable value, we were modifying the attribute on the class itself. So if two items had the same class, when we added the data-target-id the second time (different name, same class), we were overwriting our previous data-target-id. This fixed it:
def fields(self, key, items):
for (name, cls) in items.items():
for field in dir(cls):
raw_value = getattr(cls, field)
if isinstance(raw_value, Field):
value = deepcopy(raw_value)
new_name = '__'.join([key, name, field])
value.widget.attrs = {
'data-target-id': name,
}
yield (new_name, value)
Remember, it's not that the variable name ('value' in this example) was reused. That's being completely replaced each loop. If this hadn't been in a loop, say, and two variable names were used, we would still have the same problem: the new variable name would still be referencing the exact same object in memory.
In this case widget attributes on the copied attribute was itself an object, so copy wasn't enough, and we had to use deepcopy.
Many languages treat objects the same way (a variable is a reference to the object in memory), but in python everything is an object— albeit some are mutable and some, like strings, are immutable. For this reason in Python what is happening goes by a slightly different name, call by object.
When dealing with a list, instead of copy() you can just use list().
Comments
Post new comment