for i, e in enumerate(lst[:]): if e < 0: del lst[i]
Or you could do it by making a new list:
newlst = [] for e in lst: if e >= 0: newlst.append(e) return newlst
But you can always transform a "l=[] … l.append … return l" into a list comprehension:
return [e for e in lst if e >= 0]
Or, in many cases, a call to map, filter, or something out of itertools:
return filter(lambda e: e >= 0, lst)
But there's no equivalent way to simplify the mutating version. You have to write an explicit for loop. You also have to make a copy of the list, because you can't change the shape of a collection while iterating over it (this wouldn't be the case for a map-equivalent that just changed some of the values without inserting or deleting any). And you have to use enumerate and explicitly refer to lst[i] to change anything, rather than just referring to e.
There are a few shortcuts for mutating lists, such as remove, but nothing flexible or general.
And you can, of course, abuse the non-mutating tools in some cases, but there are also many cases where this doesn't work, and it's clear that this is considered abuse even when it does work. (Consider the fact that map and filter now return iterators instead of lists, which means if you want to abuse them for their side-effects, nothing actually happens, unless you borrow the consume function from the itertools docs recipes and pass the iterator into a deque(maxlen=0) or something equivalent.)
It's not that these operations would be hard to define, explain, or implement. Look at C++, which has matched pairs for everything. Instead of map and filter, they have transform and remove_if_copy—and, right alongside those, they have for_each and remove_if, which mutate in place. In fact, just about every algorithm in <algorithm> has an in-place variant.
Of course you could point out that C++ is designed for people who want programming to be difficult. After all, they call all of the non-mutating algorithms "modifying", and some but not all of the mutating-in-place algorithms "non-modifying". And sometimes the matched pairs are "foo" vs. "foo_copy", other times "inplace_foo" vs. "foo", and other times completely unrelated names like "for_each" vs. "transform".
But the point is, Python could easily have a map_inplace or for_each, extend replace with replace_if, or allow one-liner list modifiers using comprehension-style syntax. And it doesn't. Why?
Well, first, there's historical reasons. Python borrowed map, filter, and friends from mostly-immutable mostly-functional languages, and list comprehensions from a purely-immutable pure-functional language. The kind of people who wanted to use them didn't want to mutate anything. Meanwhile, C++, borrowed the same ideas from the same sources but redesigned them to fit into a more C-ish imperative-mutating style of programming, but pretty much nobody used them, so nobody was going to borrow them into other languages.
But after years of use, list comprehensions, generator expressions, map, etc. have become common, idiomatic Python that even novices understand—even though those novices mostly think of mutating algorithms first as a matter of course. So, why don't they all demand mutating equivalents? Why didn't Python 3 include them?
There's a conservative explanation. Look at the two functions at the top of the page. The first one does take 3 lines to do something that could be done in 1, but they're all very simple lines that say exactly what they do, and there's really nothing that gets in the way of reading the actual logic. The second one, however, is half boilerplate. Only half of it says what it does, and the other half is unimportant details about how it does it. Reducing line count for its own sake is not worth changing syntax for; reducing boilerplate to make code more readable is.
There's also a more radical explanation. If you look at the major changes in Python 3, other than the Unicode stuff, most of them are about replacing list-based code with iterator-based code. And there's a good reason for this: When you write everything in terms of iterator transformations, you can pipeline functions value-by-value instead of collection-by-collection. And that turns out to be pretty cool, for a number of reasons, enough to deserve its own separate post. And that's enough to add tools to help the non-mutating style.
View comments