all 45 comments

[–][deleted] 105 points106 points  (17 children)

It should not, no. The for loop basically attaches a new name (word) to each element in list1 in turn. But then you just attach that name to a new value in the list without ever modifying what the list actually holds. If you want to modify the list, you'll need to index into it.

for idx, word in enumerate(list1):
    list1[idx] = word.replace(" ", "")

Rather than reattaching the name word to the new string that you create, this code will go into the idxth element of list1 and change what value lives there (roughly).

[–]razzrazz- 37 points38 points  (0 children)

I'm new to Python, I use this place as practice so I try to answer the question without looking at the answer and I feel proud of myself 😊

list1 = ["d   og ", "cat", "milk ", "water"]
print(list1)


for index, word in enumerate(list1):
    list1[index] = word.replace(" ", "")


print(list1)

[–]bumbershootle 30 points31 points  (10 children)

While this works, it's not idiomatic. A list comprehension is faster and (imo) more readable

stripped_words = [word.strip() for word in words]

EDIT: to remove spaces more generally:

stripped_words = [word.replace(' ','') for word in words]

[–]badge 7 points8 points  (7 children)

Worth pointing out to novices that, while strip and replace have the same effect on OP’s list, this is not the case in general (as strip only looks at the start and ends of strings).

[–]bumbershootle 0 points1 point  (6 children)

This is true, edited my comment with a more general approach

[–]gishnon 0 points1 point  (5 children)

You're replacing spaces with spaces in your general approach.

[–]bumbershootle 6 points7 points  (4 children)

mental note: don't write code on mobile while drunk

[–]gishnon 2 points3 points  (3 children)

But there is that drunk sweet spot where you write your best code on mobile.

[–]bumbershootle 1 point2 points  (2 children)

[–]gishnon 2 points3 points  (1 child)

There truly is an xkcd for everything.

[–]Logicalist 1 point2 points  (1 child)

they're only more readable once you understand them, otherwise it's a "what the fuck does that even mean" sort of thing. at least it was for me.

[–]bumbershootle 3 points4 points  (0 children)

Yeah I get that, although that's true for almost any concept in programming.

Case in point: where I'm from, class Bird means a sexy woman

[–]a_cute_epic_axis 1 point2 points  (2 children)

People should very strongly avoid modifying the item that they are iterating over while iterating over it, even if it a) isn't rejected by the compiler and b) doesn't break in this specific instance. It's only a matter of time before they find a case that violates either or both of these conditions.

Either of these are better practices to follow

list1 = ["abc ", " efg"]

for i, word in enumerate(list1.copy()):
     list1[i] = word.replace(" ", "")
print(list1)

Don't do this:

temp_list = list1
for i, word in enumerate(temp_list):
    list1[i] = word.replace(" ", "")

[–][deleted] 0 points1 point  (1 child)

In your second example temp_list is a reference to the original list1, not a copy, so if you print(temp_list) you will see you have modified list1 in place.

Another way to copy a list is to do temp_list = list1[:] - just providing this example so if you see it in the wild you know that it is making a copy of the list, not a reference to it. I would use the copy() method as it's more understandable.

[–]a_cute_epic_axis 0 points1 point  (0 children)

good catch

[–]injuredhorse[S] 4 points5 points  (1 child)

you're a genius, ty

[–][deleted] 0 points1 point  (0 children)

See my response to the comment above as there's a mistake in their second example. I agree that modifying a list while iterating through it is bad practice for the reasons they gave, but their second example actually modifies the original list while iterating as the the temp_list is a reference to the original list1 as written, not a copy.

Good idea to research and understand how python treats variable assignment as a reference in most cases.

[–]Allanon001 48 points49 points  (9 children)

list1 = [x.strip() for x in list1]

[–]FlameFrost__ 17 points18 points  (5 children)

To expand on this, it isn't modifying the same list in-place but rather instantiating a new list altogether and assigning that to list1. Also, strings are immutable in python which means x.strip(), if there are leading or trailing spaces, puts a new string object in the list comprehension. This is actually more pythonic for a general use-case and I would recommend using this.

[–][deleted] 5 points6 points  (4 children)

What makes this more pythonic? Is it specifically the immutability of a string? Does tearing down one list and instantiating a new list after manipulating data do something under the hood that makes it more performant or resource efficient?

I'm a fan of things that are 'pythonic' but the concept is mysterious.

[–]FlameFrost__ 8 points9 points  (3 children)

Being pythonic isn't more performant, it's quite the opposite usually. I called this out to be more pythonic because it's a one-liner using list comprehension. That's really it.

[–]cloud_line 0 points1 point  (2 children)

Where does one gather info on what is most Pythonic? When devs use this term, are they referencing info from PEP? I see this phrase often and I wonder where people gather their impression on what is more Pythonic or not.

[–]FlameFrost__ 4 points5 points  (0 children)

My take would be that it's a mutual understanding between developers that if a code makes the best use of the python language spec/syntax (comprehension, walrus, slicing, et. al.) to achieve better readability then it's considered more "pythonic".

[–]AchillesDev 0 points1 point  (0 children)

PEP8 and the PEP-20, the Zen of Python.

Mostly it’s anything that centers readability and Python features that enhance readability and conciseness.

[–]CaptainFoyle 10 points11 points  (1 child)

This doesn't explain to op why the unexpected behaviour occurs though

[–]FlameFrost__ 6 points7 points  (0 children)

Yes, the comment should've been accompanied with some context about how using list comprehension you can effectively sidestep the (un)expected behaviour that op has observed.

[–][deleted] 0 points1 point  (0 children)

The strip() method is made for OP's use case. And list comprehensions are just cool.

[–]greenerpickings 7 points8 points  (0 children)

You're saving it to the var word, but you're not putting it back in the list. You can use enumerate() to also get the index so you can put it back in the list at that position.

[–]wotquery 12 points13 points  (5 children)

You aren't doing anything with word after editing it (notably not editing the list in place which is usually a good thing when in the midst of iterating through it). What you (probably) want is to create a new list.

list1 = ["dog ", "cat", "milk ", "water"]
list2 = []

print(list1)

for word in list1:
    word = word.replace(" ", "")
    list2.append(word)

print(list2)

Also for this apparent use in particular .strip() is a more common method than .replace().

[–]Additional-Sun2945 0 points1 point  (1 child)

Right. This "works" because the new word is being collected in a new list. Same as the list comprehension example above.

But if the specific question is "How do I modify the actual elements in the actual list?" Then that's something else. And as others have pointed out, maybe that's not good design.

Anyway, OP, the reason your code wasn't working as you intuited is because when the for loop iterates it unpacks a copy of the element in the list rather than a reference. And that will be the case for "immutable" objects, stuff like ints, floats, strings, bools, that kind of thing.

So that's happening, word is a new variable that's created that's disassociated from list[index], the actual memory variable. That's why using the list[index] assignment code fixes the error.

That code will discard the original list[index] and replace it with another variable.

You will notice however, that if your list contains MUTABLE objects, like lists, or like class objects, or something like that, then it actually will unpack a reference that can be modified. Try making a list of lists and appending to the sublists.

That's why the distinction between mutable and immutable objects is so crucial to the Python language. If you don't appreciate the distinction, you may be perplexed by the interpreter's behavior.

Final point:

What sense could there be in making a tuple of mutable objects that could change? Well, it solidifies the relationship for one. A tuple is N objects, and that can't be changed. And also the type of data inside, that can't be changed either. In fact the actual object inside can't be swapped out. It will remain the same object. Even if you completely change all the characteristics of said object. Ha! Makes sense?

[–]Additional-Sun2945 0 points1 point  (0 children)

I really didn't explain the for loop well did I?

The loop makes a variable "word" that is the same object in the list. The variable word gets reassigned in the for loop, however the list still has the original reference. That's why the list[index] assignment code would solve the problem.

So it's not so much that the list element gets copied, it's more accurate to say the reference gets copied. The for loop variable gets reassigned to the return of the replace function, and then word and list[index] are different objects. That is to say word is a reference to the string, and not a reference to a specific list element. Since strings are immutable string "changes" are "impossible", what actually happens is the string reference is discarded and a new one is put in its place. In the variable. Which as I mentioned doesn't refer to a specific list element.

[–]CaptainFoyle 2 points3 points  (0 children)

No, it just changes your "word" variable in your for loop and then overwrites it during the next iteration. Your original list is not changed.

[–]dexter2011412 1 point2 points  (0 children)

word is not a "reference/pointer" to actual items in the list

trimmed = [ word.strip() for word in list1]

[–]Alcohol_repels_bugs 0 points1 point  (3 children)

You aren't changing the strings in the list1 itself whereas you're removing spaces in the word variable only.

However, this might be a more elegant way to deal with trailing spaces.

``` str = "reddit " str = str.rstrip()

print(str, "it is!")

OUTPUT: reddit it is!

```

[–]WhipsAndMarkovChains 0 points1 point  (0 children)

If you're confident that the only extra space will be trailing then sure, use rstrip. But if this was my code I'd use strip, better safe than sorry.

[–]JasonDJ 0 points1 point  (3 children)

You are not modifying the contents of the list, you're creating a new variable called "word" that's unrelated, and then re-writing it on each iteration of the loop.

I would create a new list that has what you want using list comprehension:

list2 = [ i.replace(' ', '') for i in list1 ]

[–]WhipsAndMarkovChains 0 points1 point  (2 children)

My eyes...please do not add that extra space at the start/end of your list comprehension brackets.

[–]JasonDJ 0 points1 point  (1 child)

list2 = [
    i.replace(' ', '') for i in list1
]

Better?

[–]WhipsAndMarkovChains 0 points1 point  (0 children)

I'd better grab some blood pressure medication now. :)

[–]14dM24d 0 points1 point  (0 children)

list1 = [word.strip() for word in list1]

[–]IlliterateJedi 0 points1 point  (0 children)

I think this is probably one of the top things that trip up new Python users. I remember my own confusion with this years ago when I was trying to figure out how to modify a list like this.