0

Let's take this code that prints all positive integer of a list:

l = [1, -1, 1, 0, 2]
for i in l:
   if i > 0:
       print(i)

I can do it with a list comprehension but I guess this has the disadvantage to create a new useless list:

[print(i) for i in l if i > 0]

So my question: is there a more pythonic way to write this?

qouify
  • 3,698
  • 2
  • 15
  • 26
  • 6
    Actually using a [list comprehension for side-effects is considered un-Pythonic](https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects), so your example list comprehension would be frowned upon. – DarrylG Jul 06 '20 at 15:56
  • 3
    There is nothing unpythonic about the loop you already have. – chepner Jul 06 '20 at 16:01

4 Answers4

2

The plain for-loop is perfectly Pythonic. You want to loop over the elements of the list, select the ones that are greater than zero, and print them, and that's exactly what it does - no more, no less.

The list comprehension is not Pythonic, mostly for the reason you gave: it creates a new useless list. Even if you were going to use the list, using a list comprehension for side effects is still bad practice.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • thanks for the referenced post, that's exactly my problem here. I did'nt find it. Actually, my question was not very well asked. I'm not a python expert and I was wondering if the language provides a more succinct way to write this for-loop. – qouify Jul 06 '20 at 16:44
2

The most Pythonic way to apply a function to some subset of a list of elements is to use a for loop, just as you already have.

Within that for loop, there is an argument to be made for filtering the list before assigning any value to i; whether that constitutes an improvement is usually a matter of opinion and will depend on the context.

for i in filter(lambda x: x > 0, l):
    print(i)

In this case, I think it's worse. But sometimes you have a predicate at hand, and filtering can be syntactically lighter. Compare

for i in some_list_of_strings:
    if i.isdigit():
        print(i)

with

for i in filter(str.isdigit, some_list_of_strings):
    print(i)
chepner
  • 497,756
  • 71
  • 530
  • 681
1

I suppose that theoretically you could contrive to use a generator comprehension in order to avoid creating a large list in memory:

for _ in (print(i) for i in l if i > 0): pass

(maybe also using some function that does the consuming of values from the generator, so that any loop is hidden away inside that function).

However, not only is this less readable than the explicit for loop, the plain for loop is also quicker.

import time

l = [1, -1, 1, 0, 2]

# we don't really want lots of output for this timing test
def dont_really_print(i):
    return i

t1 = time.time()

for x in range(1000000):
    for i in l:
        if i > 0:
            dont_really_print(i)

t2 = time.time()

for x in range(1000000):
    for _ in (dont_really_print(i) for i in l if i > 0):
        pass

t3 = time.time()

print(f"generator comprehension {t3 - t2:.3f} "
      f"explicit loop {t2 - t1:.3f}")

gives:

generator comprehension 0.629 explicit loop 0.423
alani
  • 12,573
  • 2
  • 13
  • 23
  • thanks for sharing these results. For the record, I also made the test with ```for x in range(1000000): for _ in filter(lambda i: i > 0, l): dont_really_print(i)``` and it did slightly worst than the two others: 0.762 (generator), 0.562 (loop) and 0.935 (filter loop). – qouify Jul 07 '20 at 07:13
0

Generally, whether Python or any other language, what you want to do is known as a "map."

Print is a weird function to map, so here's another function for better demonstration purposes.

def add_one(num):
    return num + 1

foo = [1, 2, 3, 4]
new_list = map(add_one, foo)
print(list(new_list))

You can typically map in parallel, sometimes asynchronously, and a good number of parallel processing paradigms (including Python's multiprocessing) use map as a fundamental operation for using multiple cores to implement a function.

Andrew Holmgren
  • 1,225
  • 1
  • 11
  • 18
  • 1
    Thanks for your answer. I know of map but my problem is actually to apply some "void" function to the list items that do not return anything (or always None). Hence my example with print. – qouify Jul 06 '20 at 15:58
  • 2
    The main goal is to not create another list, and this does. – Red Jul 06 '20 at 16:03
  • @qouify Ah, okay, I think I misinterpreted your question. The for loop seems fine, you could make it a generator if you want. – Andrew Holmgren Jul 06 '20 at 16:05