TransWikia.com

"List Comprehensions" vale a pena?

Stack Overflow em Português Asked by Gui Reis on September 26, 2021

O uso de List "Comprehensions" em Python eh um bom uso? Afeta a performance do programa (tempo de execução, etc..)?

Alguns exemplos:

# 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]

Por exemplo, no segundo caso estou criando uma lista (não armazenando ela) só para mostrar no terminal os valores, porém essa lista fica [none, none..] (acrescenta um none a cada impressão)

Seria melhor usar o jeito tradicional, deixando de criar uma lista com valores nulos (apesar de não armazenar ela numa variável) ou não faz diferença?

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

2 Answers

O uso de List "Comprehensions" em Python eh um bom uso?

Sim, é um bom uso. A ideia principal sempre foi deixar as coisas mais legíveis e concisas - então, a não ser que seja uma expressão complexa, que fique mais legível em múltiplas linhas, seu uso é recomendado - os casos mais simples de list-comprehensions trocam 3 ou 4 linhas de código por uma única linha com menos de 60 colunas. Vale muito a pena.

Afeta a performance do programa (tempo de execução, etc..)?

A diferença é mínima - mas se a ideia é criar uma lista, as list-comprehension são um pouco mais rápidas do que criar uma lista vazia e ir fazendo append - só por que o construtor da lista executa o iterador do "for" direto em código nativo - sem uma chamada ao ".append" pra cada elemento.

Essa diferença é realmente mínima, e não vai ser sentida pra listas de 10, 100 ou 200 elementos, ou trechos de código que sejam executados uma única vez.

Pra listas maiores, em um trecho de programa que rode constantemente (por exemplo, em uma view de uma aplicação web com vários acessos concorrentes), pode começar a fazer diferença real.

Já o uso que você faz, para fazer o "print", realmente não é o mais usual - normalmente se usa list-comprehension quando você quer os valores mesmo. Mas quando estamos no modo interativo (seja no ipython, em um notebook, ou no pdb), essa sintaxe pod ser um bom atalho pra ver o conteúdo de várias coisas - e aí não há contra-indicações.

Mesmo que fosse uma lista com algumas dezenas de milhares de elementos "None", a diferença de performance/consumo de memória para uma única execução sequencial é negligível, quando comparamos o tamanho dessa estrutura com todo o processo da app Python, que inclui o runtime do cpython: não vai fazer nem cócegas

Apesar disso, é importante ter em mente que uma construção muito parecida com as list-comprehensions, as "generator expressions", funcionam de forma muito mais racional nesse sentido - é a mesma coisa, mas sem ser delimitada por [ e ], e sim, só por parênteses (e as vezes até sem nada, se o contexto permitir).

As generator expressions só avançam um passo no seu "for" quando um elemento vai ser "consumido" por uma estrutura de fora delas- e não guardam o elemento - então não há esse "gasto" de memória.

Só que uma generator expression sozinha também não é executada - alguém tem que "consumir" seu conteúdo.

Ou seja:

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

não faz nada: o Python prepara o objeto, pronto para criar os valores de x e chamar o "print", mas enquanto um for ou uma outra função para onde você passar essa generator expression como parâmetro não consumir os valores, o for nunca avança e o print não é executado. (A função "next" avança um generator de 1 em 1 se alguém optar por fazer o que o for faz manualmente)

Por exemplo, a função any espera um iterável, e ela consome todos os valores até chegar no primeiro que não seja equivalente a um "False" booleano. Como o "print" retorna "None", ela consumiria isso até o final:

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

(perceba também que se não houver ambiguidade, não é preciso por um par de parenteses exclusivo para a generator expression, como mencionado acima).

Um padrão de uso mais comum é, em vez de colocar o print dentro da estrutura de repetição, manter a estrutura de repetição só gerando os valores desejados, e passa-la como parâmetro para o método join de uma string, que já cuida da formatação.

O único detalhe é que cada elemento de um iterador que o "join" consome tem que ser uma string.

Mas, resumindo, isso aqui é considerado de "boa qualidade" para estar em código de produção (embora isso seja um pouco subjetivo):

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

E só pra manter a resposta completa, é importante lembrar que também existem os "dict comprehension": {chave: valor for chave, valor in iteravel} e os "set comprehensions": {elemento for elemento in iteravel} - as mesmas ideias se aplicam para ambos.

Correct answer by jsbueno on September 26, 2021

Python é uma linguagem lenta e as pessoas trabalham com isso, então esse preocupação não é tão importante. Se a performance for tão importante assim é caso de usar outra linguagem.

O mecanismo em si não é caro então não deve ter medo de usá-lo para fazer o que ele costuma ser útil que é criar uma lista nova de forma simplificada.

Não deve fazer isso só para economizar digitação ou em alguns casos nem isso.

A criação de uma lista quando não precisa de uma é bastante ineficiente. Se for um script mesmo, na maioria das vezes, não importa muito. Se estiver fazer mais que isso e a lista for grande pode ser um exagero fazer o código que fez porque ele cria uma lista sem necessidade e sem ganho algum.

O que poderia dizer é que ficou em uma linha, e acho uma falha da linguagem não permitir isto (colocando a forma declarativa para comparar e mostrar que fica igual):

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

Coloquei no GitHub para referência futura.

Fizeram uma exceção no compilador para não aceitar isso, ou seja, deliberadamente escolherem que isso não fosse aceito por causa da filosofia "só ter uma forma de fazer" que sempre é violada, mas esse caso que havia vantagens ter outra forma não aceitaram.

Answered by Maniero on September 26, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP