Search code examples
pythonlistfor-loop

Could someone explain why appending a list with the list method in a for loop stops the last element from only getting appended?


I started to make a program that reorders the letters in a string in different ways to practice some Python. I noticed that when using the .append() method in a for loop, only the last iteration was getting appended again and again. I searched for an explanation but all I could find was as solution to my issue but no good explanation to why it works.

My original code is:

def foo(word):

    wordlist = list(word)
    wordlist_copy = wordlist.copy()
    
    combinations = []
    for char in wordlist:
        for i,let in enumerate(wordlist_copy):
            wordlist_copy[i] = char
            print(wordlist_copy)
            combinations.append(wordlist_copy)     
            
    return combinations

For the string 'ba' I expected what the print statement is displaying to be stored in combinations.

This being [['b', 'a']['b', 'b']['a', 'b']['a', 'a']].

However what I got was [['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a']]

The solution I found was to change line 11: combinations.append(wordlist_copy) to combinations.append(list(wordlist_copy)).

Why is it that using the list method fixes my issue? I am already appending the wordlist_copy list on each iteration of the loop, so I can't understand how the method would do anything special here.


Solution

  • wordlist_copy is a named reference to a list. combinations.append(wordlist_copy) appends another reference to that list to the combinations list. Now you have multiple references to the same list. Change a value through one reference and all other references will see that same change because it's only one underlying object. Print out the references in combinations while your program is running and you'll see the problem:

    def foo(word):
    
        wordlist = list(word)
        wordlist_copy = wordlist.copy()
    
        combinations = []
        for char in wordlist:
            for i,let in enumerate(wordlist_copy):
                wordlist_copy[i] = char
                print(wordlist_copy)
                combinations.append(wordlist_copy)
                print("----", "ITERATION", i, "----")
                for c in combinations:
                    print(id(c), c)
                
        return combinations
    
    
    foo("ab")
    

    prints

    ['a', 'b']
    ---- ITERATION 0 ----
    139817235422848 ['a', 'b']
    ['a', 'a']
    ---- ITERATION 1 ----
    139817235422848 ['a', 'a']
    139817235422848 ['a', 'a']
    ['b', 'a']
    ---- ITERATION 0 ----
    139817235422848 ['b', 'a']
    139817235422848 ['b', 'a']
    139817235422848 ['b', 'a']
    ['b', 'b']
    ---- ITERATION 1 ----
    139817235422848 ['b', 'b']
    139817235422848 ['b', 'b']
    139817235422848 ['b', 'b']
    139817235422848 ['b', 'b']
    

    By that final iteration, you have 4 references to a single list.

    Your code keeps appending a reference to the single wordlist_copy list to the combinations list.

    Since you haven't make new copies for combinations, changes to wordlist_copy are seen by all of the references in combinations.

    list(wordlist_copy) fixes the problem because it creates a new list which to add to the list. It does the same thing as wordlist_copy.copy().