Why can't this function work to replace elements in a list?

Question:

Trying to change all 5’s into 100’s. I know you should use list comprehension but why doesn’t this work? Someone can explain theoretically? Thank you.

d = [5,1,1,1,5]

def f1(seq):
    for i in seq:
        if i==5:
            i = 100
    return seq

print (f1(d))
Asked By: MrSoLoDoLo

||

Answers:

This line:

i = 100

Gives the local variable i, which was originally assigned that value in seq, the value 100.

To change the value in the sequence, you could do:

for index, object in enumerate(seq):
    if object == 5:
        seq[index] = 100

Enumerate returns two objects each time it is called on a sequence, the index as a number, and the object itself.

See the docs on lists and (Python 2) enumerate.

You could have also written:

for index in range(len(seq)):
    if seq[index] == 5:
        seq[index] = 100

Which you may prefer, but is sometimes considered less clean.

Answered By: cat

Take the following example:

def f1(seq):
    for i in seq:
        if i==5:
            i = 100
        # at this point (assuming i was 5), i = 100 but seq is still [3,5,7]
        # because i is not a reference to the item but the value *copied* from the list
    ...
f1([3,5,7])

You could instead loop through the indices and set the value at that index in the list:

d = [5,1,1,1,5]

def f1(seq):
    for i in range(len(seq)):
        if seq[i]==5:
            seq[i] = 100
    return seq

print(f1(d))
# [100,1,1,1,100]

You should update the element at the list, like that:

def f1(seq):
    for i in range(len(seq)): # run through the indexes of the list
        if seq[i]==5: # check whether seq at index i is 5
            seq[i] = 100 # update the list at the same index to 100

    return seq

i is a new variable created inside the loop, therefore it’s not the same reference as the element inside the list.

NOTE:

Note that list is a mutable object, therefore changing seq inside the function will affect the list even outside the function.

You can read more about mutable and immutable in here

Answered By: omri_saadon

The Python assignment operator binds a value to a name. Your loop for i in seq binds a value from seq to the local name i on every iteration. i = 100 then binds the value 100 to i. This does not affect the original sequence, and the binding will be changed again in the next iteration of the loop.

You can use enumerate to list the indices along with the values of seq and perform the binding that way:

def f1(seq):
    for i, n in enumerate(seq):
        if n == 5:
            seq[i] = 100
    return seq

Even simpler may be to just iterate over the indices:

def f2(seq):
    for i in range(len(seq)):
        if seq[i] == 5:
            seq[i] = 100
    return seq

The options shown above will modify the sequence in-place. You do not need to return it except for convenience. There are also options for creating a new sequence based on the old one. You can then rebind the variable d to point to the new sequence and drop the old one.

The easiest and probably most Pythonic method would be using a list comprehension:

d = [5, 1, 1, 1, 5]
d = [100 if x == 5 else x for x in d]

You can also use map:

d = list(map(lambda x: 100 if x == 5 else x, d))

The output of map is a generator, so I have wrapped it in list to retain the same output type. This would not be necessary in Python 2, where map already returns a list.

Answered By: Mad Physicist
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.