O Python otimiza uma variável que é usada apenas como um valor de retorno?

106

Existe alguma diferença final entre os dois trechos de código a seguir? O primeiro atribui um valor a uma variável em uma função e, em seguida, retorna essa variável. A segunda função apenas retorna o valor diretamente.

O Python os transforma em bytecode equivalente? É um deles mais rápido?

Caso 1 :

def func():
    a = 42
    return a

Caso 2 :

def func():
    return 42
Jayesh
fonte
5
Se você usar dis.dis(..)em ambos, verá que há uma diferença , então sim. Mas, na maioria dos aplicativos do mundo real , a sobrecarga disso em comparação com o atraso do processamento na função não é muito.
Willem Van Onsem
4
Existem duas possibilidades: (a) Você vai chamar esta função muitas (ou seja, pelo menos um milhão) de vezes em um loop fechado. Nesse caso, você não deve chamar uma função Python, mas sim vetorizar seu loop usando algo como a biblioteca numpy. (b) Você não vai chamar essa função muitas vezes. Nesse caso, a diferença de velocidade entre essas funções é muito pequena para que valha a pena se preocupar.
Arthur Tacca

Respostas:

138

Não, não importa .

A compilação para o código de byte CPython é passada apenas por um pequeno otimizador de olho mágico que é projetado para fazer apenas otimizações básicas (consulte test_peepholer.py no conjunto de testes para mais informações sobre essas otimizações).

Para ver o que realmente vai acontecer, use dis* para ver as instruções geradas. Para a primeira função, contendo a atribuição:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Enquanto, para a segunda função:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

Mais duas instruções (rápidas) são usadas na primeira: STORE_FASTe LOAD_FAST. Isso faz um armazenamento rápido e captura do valor na fastlocalsmatriz do quadro de execução atual. Então, em ambos os casos, a RETURN_VALUEé executado. Portanto, o segundo é ligeiramente mais rápido devido a menos comandos necessários para executar.

Em geral, esteja ciente de que o compilador CPython é conservador nas otimizações que realiza. Não é e não tenta ser tão inteligente quanto os outros compiladores (que, em geral, também têm muito mais informações para trabalhar). O principal objetivo do design, além de obviamente estar correto, é a) mantê-lo simples eb) ser o mais rápido possível na compilação para que você nem perceba que existe uma fase de compilação.

No final, você não deve se preocupar com pequenos problemas como este. O benefício na velocidade é mínimo, constante e, diminuído pela sobrecarga introduzida pelo fato de que o Python é interpretado.

* disé um pequeno módulo Python que desmonta seu código, você pode usá-lo para ver o bytecode Python que a VM executará.

Nota: Como também afirmado em um comentário de @Jorn Vernee, isso é específico para a implementação CPython do Python. Outras implementações podem fazer otimizações mais agressivas se assim o desejarem, o CPython não.

Dimitris Fasarakis Hilliard
fonte
11
Não sou uma pessoa python (c ++), então não sei como funciona nos bastidores, mas o primeiro caso não deveria ser otimizado para o segundo? Um compilador C ++ decente faria essa otimização.
NathanOliver
7
@NathanOliver realmente não funciona, Python fará o que foi dito aqui, mesmo sem tentar ser inteligente.
Dimitris Fasarakis Hilliard
80
O fato de que a suposição perfeitamente razoável e inteligente de @NathanOliver em uma resposta a esta pergunta esteja completamente errada é, aos meus olhos, uma prova de que esta não é uma pergunta "autoexplicativa", "nonsense" ou "estúpida" que pode ser respondida "parando um momento para pensar", como TigerhawkT3 nos faz acreditar. É uma pergunta válida e interessante para a qual eu não tinha certeza da resposta, apesar de ter sido um programador Python profissional por anos.
Mark Amery de
O compilador de Python é, na melhor das hipóteses, 'conservador', não 'muito conservador'. O objetivo principal do design não é ser "o mais rápido possível ... para que você nem perceba que existe uma fase de compilação." Isso é secundário, depois de "manter a simplicidade". Uma função com constantes grandes como "1 << (2 ** 34)" e "b'x '* (2 ** 32)" leva vários segundos para compilar e gerar constantes de tamanho GB, mesmo se a função nunca for corre. A string grande será até mesmo descartada pelo compilador. As correções propostas para esses casos foram rejeitadas, pois tornariam o compilador muito complexo.
Andrew Dalke
@AndrewDalke, obrigado pelo comentário interno sobre isso, ajustei o texto para resolver os problemas que você apontou.
Dimitris Fasarakis Hilliard
3

Ambos são basicamente iguais, exceto que, no primeiro caso, o objeto 42é simplesmente atribuído a uma variável nomeada aou, em outras palavras, nomes (isto é a) referem-se a valores (isto é 42). Ele não faz nenhuma atribuição tecnicamente, no sentido de que nunca copia nenhum dado.

Enquanto returning, esta ligação nomeada aé retornada no primeiro caso, enquanto o objeto 42é retornado no segundo caso.

Para mais leitura, consulte este ótimo artigo de Ned Batchelder

kmario23
fonte