14

I have a list of strings that I need to sanitize. I have a method for sanitizing them, so I could just do:

new_list = map(Sanitize, old_list)

but I don't need to keep the old list around. This got me wondering if there's an in-place equivalent to map. Easy enough to write a for loop for it (or a custom in-place map method), but is there anything built in?

Cœur
  • 32,421
  • 21
  • 173
  • 232
Herms
  • 34,065
  • 11
  • 75
  • 100
  • 1
    Yep, this is a dup. I swear I searched to see if this was already asked. Apparently my searching skills aren't good enough yet. :) – Herms Nov 10 '10 at 19:47
  • 4
    Not a meaningful duplicate, since that question wasn't answered. Old questions are unlikely to be answered (there's no way to "bump" questions), so it doesn't make sense to close this as a duplicate. Closing dupes like that would effectively doom them to never being answered. – Glenn Maynard Nov 10 '10 at 19:49
  • @Glenn Maynard, voted to close because now there's an answer. – aaronasterling Nov 10 '10 at 20:03
  • 2
    @aaronasterling: No, there is no answer to this question in #3000461. This question has been closed in error. – Glenn Maynard Nov 10 '10 at 20:06
  • @Glenn Maynard Dammit you're right. Voted to reopen and voted to close the older one. – aaronasterling Nov 10 '10 at 20:11
  • 1
    This still creates a new list, but reuses the reference of old_list `old_list[:] = map(Sanitize, old_list)` – John La Rooy Nov 10 '10 at 20:20
  • @gnibbler: Not actually in-place, but since the OP's real goal (judging from comments below) seems to be "put the results in the same list given by the caller" and not caring so much whether it's actually in-place, that's probably OK. I have some intuitive hesitation, though; I'd probably still just write a function that actually does it in place. – Glenn Maynard Nov 10 '10 at 20:31
  • @gnibbler So something else that references the list would see the change? Interesting. – Herms Nov 10 '10 at 20:31

4 Answers4

12

The answer is simply: no.

Questions of the form "does XXX exist" never tend to get answered directly when the answer is no, so I figured I'd put it out there.

Most itertools helpers and builtins operate on generic iterators. map, filter, list comprehensions, for--they all work on iterators, not modifying the original container (if any).

Why aren't there any mutating functions in this category? Because there's no generic, universal way to assign values to containers with respect to their keys and values. For example, basic dict iterators (for x in {}) iterate over the keys, and assigning to a dict uses the result of the dict as the parameter to []. Lists, on the other hand, iterate over the values, and assignment uses the implicit index. The underlying consistency isn't there to provide generic functions like this, so there's nothing like this in itertools or in the builtins.

They could provide it as a methods of list and dict, but presently they don't. You'll just need to roll your own.

Glenn Maynard
  • 50,887
  • 9
  • 110
  • 128
6

You have to loop:

for i in range(len(old_list)):
    old_list[i] = Sanitize(old_list[i])

There's nothing built-in.

As suggested in the comments: a function as desired by the OP:

def map_in_place(fn, l):
    for i in range(len(l)):
        l[i] = fn(l[i])

map_in_place(Sanitize, old_list)
Tom
  • 36,698
  • 31
  • 90
  • 98
Ned Batchelder
  • 323,515
  • 67
  • 518
  • 625
  • Precede your for-loop with `def map_in_place(func, a_list):` and we have a complete answer. (But `old_list = map(Sanitize, old_list)`, while it doesn't mutate the old list, seems the best solution.) – Steven Rumbalski Nov 10 '10 at 20:07
2

At the end of the day,

old_list = map(Sanitize, old_list)

will get done exactly what you need.

You might object that creating a new list object and garbage collecting the old one is slower than using an existing one. In practice, this should almost never be an issue (allocating as a one-off a chunk of memory is unlikely to be a bottleneck; maybe if you are doing this in a loop or have a massively long list you could try benchmarking, but I still would wager against you). Remember that either way you have to create new strings from scratch as part of Sanitize and that's going to be much more expensive.

Worth noting, as mluebke notes, is that these days list comprehensions are considered much more "pythonic" than map (I believe map is deprecated in future versions of the language).

Edit: Ah, I see you are trying to edit the values of arguments passed to a function. I'd fairly strongly argue that this is "unpythonic" and that you should return the new list as one of your return values (remember, using tuples you can have more than one return value).

YGA
  • 7,892
  • 13
  • 37
  • 46
  • The in-place bit isn't really a garbage collection issue. The problem is that I'm inserting this sanitation inside some code that doesn't return back a result. It simply mutates the current list (there's a lot of other stuff that's modified in the list as well). So I need to mutate that. – Herms Nov 10 '10 at 19:49
1

Similar to using this[0] = something, you can also specify slices:

>>> x = [1, 2, 3, 4]
>>> x[1:3] = [0, 0]
>>> x
[1, 0, 0, 4]

Since slices can have parts left out (the start, stop, or step), you can change the whole list by something like this:

>>> x = [1, 2, 3, 4]
>>> x[:] = [4, 5, 6]
>>> x
[4, 5, 6]

This (as seen above) is capable of changing even the length of the list. As can be seen below, this is indeed changing the actual object, not redefining the variable:

>>> x = [1, 2, 3, 4]
>>> y = x
>>> x[:] = [3, 4]
>>> y
[3, 4]

It doesn't necessarily need to be a list at the right end of the assignment. Anything that is iterable can be on that side. In fact, you could even have a generator:

>>> x = ["1", "2", "3", "4"]
>>> x[:] = (int(y) for y in x)
>>> x
[1, 2, 3, 4]

or the returns of map() (a list in Python 2; a map object in Python 3):

>>> x = ["1", "2", "3", "4"]
>>> x[:] = map(int, x)
zondo
  • 18,070
  • 7
  • 35
  • 73