3

Python doc says

List comprehensions provide a concise way to create lists.

It seems that Python doc didn't say (may be this claim is incorrect, please fix it if needed)

"List comprehensions is faster than for loop"

Here are 2 functions

def find_base_match_v1(char, matrix):
    base_matches = []
    for row_index, row in enumerate(matrix):
        for column_index, column in enumerate(row):
            if char == column:
                base_matches.append((row_index, column_index))
    return base_matches

def find_base_match_v2(char, matrix):
    base_matches = [(row_index, column_index)
                    for row_index, row in enumerate(matrix)
                    for column_index, column in enumerate(row)
                    if char == column]
    return base_matches

the performance of function v1 (for loop approach)

timeit "find_base_match_v1('d', grid)"

10.6 ns ± 0.419 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

the performance of function v2 (list comprehension approach)

timeit "find_base_match_v2('d', grid)"

12.1 ns ± 1.74 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

sometimes the results are converse, v2 is a little bit faster than v1.

Any way, there is no guarantee that which one is necessarily faster than the other one, is my understanding right?

milanbalazs
  • 4,811
  • 4
  • 23
  • 45
  • 2
    It's *just concise*, i.e. *shorter to type and read*. Nothing is said about performance one way or the other… – deceze Aug 16 '19 at 09:59
  • Are you sure you measured the function call and not just string creation? Even the function call with empty grid takes `453 ns` on my machine – awesoon Aug 16 '19 at 10:07

1 Answers1

1

List comprehensions are not magic. Look at your code: it reads a matrix and stores the coordinates of the elements equal to a given char:

matrix1 = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
print(timeit.timeit(lambda: find_base_match_v1('f', matrix1)))
# 1.002808532999552
print(timeit.timeit(lambda: find_base_match_v2('f', matrix1)))
# 1.0684777589995065

The time is almost the same, for a very obvious reason: the built list is tiny

print(find_base_match_v1('f', matrix1))
# [(1, 2)]
print(find_base_match_v2('f', matrix1))
# [(1, 2)]

If you build a bigger list, let's say with hundred elements, the list comprehension beats the for loop (CPython 3.6 on Ubuntu):

matrix2 = [['f']*10 for _ in range(10)]
print(find_base_match_v1('f', matrix2))
# [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9), (7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8), (7, 9), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7), (8, 8), (8, 9), (9, 0), (9, 1), (9, 2), (9, 3), (9, 4), (9, 5), (9, 6), (9, 7), (9, 8), (9, 9)]

print(timeit.timeit(lambda: find_base_match_v1('f', matrix2)))
# 9.212440792000052
print(timeit.timeit(lambda: find_base_match_v2('f', matrix2)))
# 6.918398160998549

But as written in a comment, that's not guaranted by the doc. For more information, see: Are list-comprehensions and functional functions faster than "for loops"?

jferard
  • 7,835
  • 2
  • 22
  • 35