Vince Vince - 1 month ago 4x
Python Question

Why don't Python's list comprehensions make copies of arguments so actual objects can't be mutated?

Maybe I've been drinking too much of the functional programming Kool Aid, but this behavior of list comprehensions seems like a bad design choice:

>>> d = [1, 2, 3, 4, 5]
>>> [d.pop() for _ in range(len(d))]
[5, 4, 3, 2, 1]
>>> d

Why is
not copied, and then the copied lexically-scoped version not mutated (and then lost)? The point of list comprehensions seems like it should be to return the desired list, not return a list and silently mutate some other object behind the scenes. The destruction of
is somewhat implicit, which seems unPythonic. Is there a good use case for this?

Why is it advantageous to have list comps behave exactly like for loops, rather than behave more like functions (from a functional language, with local scope)?


Python never does copy unless you specifically ask it to do a copy. This is a perfectly simple, clear, and totally understandable rule. Putting exceptions and distinguos on it, such as "except under the following circumstances within a list comprehension...", would be utter folly: if Python's design had ever been under the management of somebody with such crazy ideas, Python would be a sick, contorted, half-broken language not worth learning. Thanks for making me happy all over again in the realization that is is definitely not the case!

You want a copy? Make a copy! That's always the solution in Python when you prefer a copy's overhead because you need to perform some changes that must not be reflected in the original. That is, in a clean approach, you'd do

dcopy = list(d)
[dcopy.pop() for _ in range(len(d))]

If you're super-keen to have everything within a single expression, you can, though it's possibly not code one would call exactly "clean":

[dcopy.pop() for dcopy in [list(d)] for _ in range(len(d))]

i.e., the usual trick one uses when one would really like to fold an assignment into a list comprehension (add a for clause, with the "control variable" being the name you want to assign to, and the "loop" is over a single-item sequence of the value you want to assign).

Functional languages never mutate data, therefore they don't make copies either (nor do they need to). Python is not a functional language, but of course there's a lot of things you can do in Python "the functional way", and often it's a better way. For example, a much better replacement for your list comprehension (guaranteed to have identical results and not affect d, and vastly faster, more concise, and cleaner):


(AKA "the Martian Smiley", per my wife Anna;-). Slicing (not slice assignment, which is a different operation) always perform a copy in core Python (language and standard library), though of course not necessarily in independently developed third party modules like the popular numpy (which prefers to see a slice as a "view" on the original numpy.array).