"List Comprehensions" is it worth it?

Asked

Viewed 197 times

5

Is the use of List "Comprehensions" in Python a good use? Affects program performance (runtime, etc..)?

Some examples:

# Lista com números pares de 0 a 10:
par = [x for x in range(11)  if x % 2 == 0]
    
# Mostra no terminal os números pares de 0 a 10:
[print(x) for x in range(11)  if x % 2 == 0]

For example, in the second case I am creating a list (not storing it) just to show the values in the terminal, but this list is [none, none..] (adds a none with each impression)

It would be better to use the traditional way, not creating a list with null values (although not storing it in a variable) or it makes no difference?

# Código tradional no segundo caso:
for x in range(11):
    if x % 2 == 0:
        print(x)
  • 1

    If it is only to print, not worth it, because you will be creating a list for nothing, not to mention I think gambiarra: https://answall.com/a/459289/112052 - Now you need to create a list with the values, ai the difference probably will be small, but performance is something that has to be analyzed on a case-by-case basis. https://stackoverflow.com/q/22108488

  • For these questions, you helped me!!

  • You can do it in a row without having to generate a list for x in range(11): print(x) if x % 2 == 0 else None but it is a gambiarra because it is a crooked use of conditional expression

2 answers

5

Python is a slow language and people work with it, so that concern is not so important. If the performance is so important it is a case of using another language.

The mechanism itself is not expensive so you should not be afraid to use it to do what it usually is useful which is to create a new list in simplified form.

You shouldn’t do this just to save typing or in some cases even that.

Creating a list when you don’t need one is quite inefficient. If you are a script even, most of the time, it doesn’t matter much. If you are doing more than that and the list is large it can be an exaggeration to do the code you did because it creates a list with no need and no gain.

What I could say is that it stayed on one line, and I think it’s a language flaw not to allow this (putting the declarative form to compare and show that it looks the same):

for x in range(11): if x % 2 == 0: print(x)
[print(x) for x in range(11) if x % 2 == 0]

I put in the Github for future reference.

They made an exception in the compiler not to accept this, that is, deliberately choose that this was not accepted because the philosophy "only have a way of doing" that is always violated, but this case that there were advantages have otherwise not accepted.

3


The use of List "Comprehensions" in Python is a good use?

Yes, it’s a good use. The main idea has always been to let the most readable and concise things - so unless is a complex expression, which is more readable in multiple lines, its use is recommended - the cases simpler list-comprehensions exchange 3 or 4 lines of code for a single line with less of 60 columns. It is very worthwhile.

Affects the program performance (execution time, etc..)?

The difference is minimal - but if the idea is to create a list, list-comprehension is a little faster than create an empty list and go append - only because the list constructor executes the iterator of the "for" straight in native code - without a call to ". append" for each element.

That difference is really minimal, and it won’t be felt for lists of 10, 100 or 200 elements, or code snippets to be executed only once.

For larger lists, in a program section that runs constantly (for example, in a view of an application web with several concurrent accesses), can start to make real difference.

Already the use you make, to make the "print", really is not most usual - usually use list-comprehension when you want the same values. But when we are in interactive mode (whether on ipython, a notebook, or pdb) this syntax can be a good shortcut to see the content of various things - and there are no contraindications.

Even if it was a list of a few tens of thousands of "None" elements, the difference in performance/memory consumption for a single sequential execution is negligible, when we compare the size of this structure with the whole process from the Python app, which includes cpython Runtime: it won’t tickle

Despite this, it is important to keep in mind that a building very similar to the list-comprehensions, the "Generator Expressions", work much more rationally in this sense - it’s the same thing, but without being delimited by [ and ], and yes, only by parentheses (and sometimes even with nothing, if the context allows).

The Generator Expressions only advance one step in their "for" when an element is going to be "consumed" by a structure outside of them- and they don’t store the element - so there is no "spent" memory.

Only a Generator Expression alone doesn’t run either - someone has to "consume" its content.

That is to say:

(print(x) for x in range(11) if x % 2 == 0)

does nothing: Python prepares the object, ready to create the values of x and call "print", but while one for or another function to which you pass this Generator Expression as a parameter does not consume the values, the for never advances and print does not run. (The "next" function advances a Generator of 1 in 1 if someone chooses to do what the for manually makes)

For example, the function any waits for an eternity, and she consumes all values until you reach the first one that is not equivalent a boolean "False". Since "print" returns "None", it would consume this until the end:

any(print(x) for x in range(11) if x % 2 == 0)

(also understand that if there is no ambiguity, there is no need by a pair of parentheses exclusive to Generator Expression, as mentioned above).

A more common usage pattern is instead to put the print inside of the repeating structure, maintain the repeating structure only generating the desired values, and pass it as a parameter to the method join of a string, which already takes care of formatting.

The only detail is that each element of an iterator that "Join" consumes has to be a string.

But, in short, this is considered "good quality" for be in production code (although this is a little subjective):

print(", ".join(str(x) for x in range(11) if not x % 2))

And just to keep the answer complete, it’s important to remember that there are the "Dict comprehension": {chave: valor for chave, valor in iteravel} and the "set comprehensions": {elemento for elemento in iteravel} - the same ideas apply to both.

  • Very much so, it took away all my doubts!! Vlw same!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.