1

I have a small function which uses one list to populate another. For some reason, the source list gets modified. I don't have a single line that manipulates the source list arr. I am probably missing the way Python deals with scope of variables, lists. My expected output is for the list arr to remain the same after the function call.

numTestRows = 5
m = 2
def getTestData():
    data['test'] = []
    size_c = len(arr)
    for i in range(numTestRows):
        data['test'].append(arr[i%size_c])
        for j in range(m):
            data['test'][i].append('xyz')

#just a 2x5 str matrix
arr = [['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'j']]
print('Array before: ')
print( arr)
data = {}
getTestData()
print('Array after: ')
print( arr)

Output

Array before: 
[['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'j']]
Array after: 
[['a', 'b', 'c', 'd', 'e', 'xyz', 'xyz', 'xyz', 'xyz', 'xyz', 'xyz'], ['f', 'g', 'h', 'i', 'j', 'xyz', 'xyz', 'xyz', 'xyz']]
Prune
  • 76,765
  • 14
  • 60
  • 81
  • 1
    It's better if you passed your variables (`data` and `arr`) as arguments to `getTestData()` – pault Sep 10 '18 at 18:00
  • Why? I know it's considered a good practice but what about cases when I need the variable across functions, global is better than passing to each one. – Vandan Kumbhat Sep 10 '18 at 18:02
  • 1
    [Why are global variables bad?](https://stackoverflow.com/questions/19158339/why-are-global-variables-evil) – pault Sep 10 '18 at 18:03
  • It appear to me that `data['test'].append(arr[i%size_c])` is appending a reference to the element of the array and `data['test'][i].append('xyz')` data to this object it has stored. – Tim Sep 10 '18 at 18:05
  • @pault I disagree that this is a duplicate question. The other question is mainly to do with the use of the * operator in creating a list which isn't relevant here. Here it appear to be how the the array is stored within the dictionary. – Tim Sep 10 '18 at 18:09
  • 1
    To fix the issue change the line `data['test'].append(arr[i%size_c])` to `data['test'].append(arr[i%size_c][:])` to make a new copy of the array when you are appending it to a dictionary rather than adding the reference to it. https://stackoverflow.com/questions/8744113/python-list-by-value-not-by-reference is about copying by value rather than by reference. – Tim Sep 10 '18 at 18:14
  • @Tim -It worked but I don't understand why ?Th e syntax you suggested conveys the same meaning .. In any case, the changes are to the data['test'] list, how does the arr list get affected without any assignment ? – Vandan Kumbhat Sep 10 '18 at 18:23
  • Does this answer your question? [List changes unexpectedly after assignment. Why is this and how can I prevent it?](https://stackoverflow.com/questions/2612802/list-changes-unexpectedly-after-assignment-why-is-this-and-how-can-i-prevent-it) – Thomas Weller Mar 04 '22 at 12:02

1 Answers1

2

You've mis-handled the references in your list of lists (not a matrix). Perhaps if we break this down a little more, you can see what's happening. Start your main program with the two char lists as separate variables:

left  = ['a', 'b', 'c', 'd', 'e']
right = ['f', 'g', 'h', 'i', 'j']
arr = [left, right]

Now, look at what happens within your function at the critical lines. On this first iteration, size_c is 2, i is 0 ...

    data['test'].append(arr[i%size_c])

This will append arr[0] to data[test], which started as an empty list. Now for the critical part: arr[0] is not a new list; rather, it's a reference to the list we now know as left in the main program. There is only one copy of this list.

Now, when we get into the next loop, we hit the statement:

        data['test'][i].append('xyz')

data['test'][i] is a reference to the same list as left ... and this explains the appending to the original list.

You can easily copy a list with the suffix [:], making a new slice of the entire list. For instance:

data['test'].append(arr[i%size_c][:])

... and this should solve your reference problem.

Prune
  • 76,765
  • 14
  • 60
  • 81