7

What is the preferred way to tell someone "I want to apply func to each element in iterable for its side-effects"?

Option 1... clear, but two lines.

for element in iterable:
    func(element)

Option 2... even more lines, but could be clearer.

def walk_for_side_effects(iterable):
    for element in iterable:
        pass

walk_for_side_effects(map(func, iterable))  # Assuming Python3's map.

Option 3... builds up a list, but this how I see everyone doing it.

[func(element) for element in iterable]

I'm liking Option 2; is there a function in the standard library that is already the equivalent?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
ToBeReplaced
  • 3,334
  • 2
  • 26
  • 42
  • 5
    You should use option 1. It's the one that best communicates that your code is imperative, and doesn't construct a useless result list. – millimoose Jan 21 '13 at 21:31
  • `map` and the list comrehension are equivalent. The `walk_for_side_effects` call is useless. Use option 1. – Pavel Anossov Jan 21 '13 at 21:33
  • 2
    @PavelAnossov: Not in python 3; `map()` returns an iterator. – Martijn Pieters Jan 21 '13 at 21:34
  • 2
    Technically, Option 1 could be done in one line as `for element in iterable: func(element)`. It's against PEP8 style, but I would say that using list comprehensions to generate a list that is never used is a worse offense. – David Robinson Jan 21 '13 at 21:36
  • @MartijnPieters: man, I need to work more with Python 3 :( – Pavel Anossov Jan 21 '13 at 21:38
  • Related: [Is it Pythonic to use list comprehensions for just side effects?](https://stackoverflow.com/q/5753597/4518341) – wjandrea Jul 07 '22 at 17:54
  • What about Option 1.1: ``` def apply(func, iterable): for element in iterable: func(element) ``` ? – joseville Aug 08 '22 at 14:35

2 Answers2

11

Avoid the temptation to be clever. Use option 1, it's intent is clear and unambiguous; you are applying the function func() to each and every element in the iterable.

Option 2 just confuses everyone, looking for what walk_for_side_effects is supposed to do (it certainly puzzled me until I realized you needed to iterate over map() in Python 3).

Option 3 should be used when you actually get results from func(), never for the side effects. Smack anyone doing that just for the side-effects. List comprehensions should be used to generate a list, not to do something else. You are instead making it harder to comprehend and maintain your code (and building a list for all the return values is slower to boot).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Someday I will understand why people think it's crude to write "for i in seq: do_something(i)` but reversing the order, removing the colon, and adding square brackets around it is the height of sophistication. Reminds me of the quick-thinking car salesman on the Simpsons when the car he's showing is suddenly hit by bullets: "These are speed holes. They make the car go faster." – DSM Jan 21 '13 at 21:47
  • 3
    It is crude because it requires an indentation block and the introduction of a variable that is not necessary. It feels extremely dirty to people used to functional programming. – ToBeReplaced Jan 21 '13 at 21:53
  • 1
    @ToBeReplaced: Functions with side-effects are not really functional programming, are they? – Martijn Pieters Jan 21 '13 at 22:01
  • 2
    @ToBeReplaced Surely building a list for the side effects of the function in the list comprehension is dirtier to functional programmers! (I find it so, anyway, and I'd rather be writing Haskell than Python) – Ben Jan 21 '13 at 22:04
  • @DSM, it's funny you should mention "speed holes make the car go faster" because one of the reasons list comprehensions are popular is they *are* frequently faster. – jimhark Jan 21 '13 at 22:16
  • @jimhark: depends entirely on the situation. If you don't care about the list itself, then the overhead not due to the function will actually be larger with a listcomp as opposed to a loop (just verified with timeit, cpython 2.7.3). – DSM Jan 21 '13 at 22:28
  • 2
    @jimhark: only if you are actually aiming to build a list. Simply looping and calling a function is faster than creating a list for the `None` return values. – Martijn Pieters Jan 21 '13 at 22:29
  • @MartijnPieters, I guess I should have mentioned they can be faster when used appropriately, i.e. to build a list. – jimhark Jan 21 '13 at 22:34
  • @DSM, "`just verified with timeit, cpython 2.7.3`". What did you use, `timeit.timeit(stmt='[func(i) for i in a]'`? Try it again with `timeit.timeit(stmt='[func(i) for i in a if False]'`. With `if False` I measure it being about 3.5 times faster than the `for` loop (calling an empty `func`). Not that speed is the most important thing here, but it can be surprising how fast list comprehensions are. – jimhark Jan 21 '13 at 22:59
  • 1
    @jimhark: You are comparing the wrong things; compare `for i in a: func(a)` with `[func(a) for i in a]` instead. Adding `if False` just means the `func(a)` expression is not executed at all. Of course the loop is fast then. Note that list comprehension 'speed' comes from how it builds the final result, it's loop runs at the same tempo as a regular for loop (same bytecode, in fact). – Martijn Pieters Jan 21 '13 at 23:02
  • @MartijnPieters Doh! You're right. With the `if false` the function is not being called (of course). So the correct list comprehension is about 20% slower. Thanks for correcting my mistake. – jimhark Jan 21 '13 at 23:09
2

This has been asked many times, e.g., here and here. But it's an interesting question, though. List comprehensions are meant to be used for something else.

Other options include

  1. use map() - basically the same as your sample
  2. use filter() - if your function returns None, you will get an empty list
  3. Just a plain for-loop

while the plain loop is the preferable way to do it. It is semantically correct in this case, all other ways, including list comprehension, abuse concepts for their side-effect.

In Python 3.x, map() and filter() are generators and thus do nothing until you iterate over them. So we'd need, e.g., a list(map(...)), which makes it even worse.

Community
  • 1
  • 1
Thorsten Kranz
  • 12,492
  • 2
  • 39
  • 56