rows = self.rows
НО! В тоже время изменяется из self.rows, который является другим списком.
Не является. Твой оператор просто присвоил переменной rows ссылку на тот же самый список, что и self.rows.
Это легко проверить оператором is или сравнением id():
print(rows is self.rows, id(rows) == id(self.rows)) # True True
Более того, если ты только скопируешь сам список:
rows = self.rows.copy()
То всё ещё поймаешь проблемы, так как скопируются ссылки на элементы, а не их значения:
print(rows is self.rows, id(rows) == id(self.rows)) # False False
print(rows[0] is self.rows[0], id(rows[0]) == id(self.rows[0])) # True True
Тебе нужно сделать глубокую копию (deepcopy). Это можно сделать вручную, так как у тебя всего два уровня вложенности (список-словарь):
rows = [rowdict.copy() for rowdict in self.rows]
Для более глубоких уровней есть функция
copy.deepcopy(), но у неё есть свои подводные камни. Цитата:
Two problems often exist with deep copy operations that don’t exist with shallow copy operations:
Recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop.
Because deep copy copies everything it may copy too much, such as data which is intended to be shared between copies.