Coletânea Python do ZERO às Redes Neurais Artificiais
Geradores e Memória
Como já mencionado algumas vezes ao longo de outros capítulos, devemos sempre prezar pela legibilidade de nosso código e pela performance do mesmo.
Um ponto importante a salientar aqui é que todo e qualquer objeto em
Python ocupará um espaço alocado em memória com tamanho dinamicamente proporcional ao seu conteúdo.
Quando declaramos um determinado tipo de dado manualmente em
Python, o mesmo tende a ter um tamanho diferente de quando declarado automaticamente, isto se dá porque ao especificar manualmente um determinado tipo de dado, implicitamente serão criadas, de acordo com o tipo de dado, alguns métodos para iteração do mesmo.
Em outras palavras, atribuir para uma variável qualquer, por exemplo,
‘Fernando’ e str(‘Fernando’) terão tamanhos diferentes, uma vez que str( ) como iterável carrega em sua estrutura interna alguns métodos ocultos do desenvolvedor, além é claro, dos dados/valores do objeto em si.
O uso de geradores, quando usados para volumes de dados
consideráveis, serve não somente para poupar tempo de codificação mas alocação de memória, pois seus dados serão gerados e alocados conforme necessidade do código.
Para que fique mais claro, vamos ao exemplo. Repare que foram criadas duas variáveis lista_manual e lista_gerada, ambas listas com 15 elementos numéricos em sua composição.
Por meio da função print( ) parametrizada com sys.getsizeof( ) por sua
vez parametrizada com cada uma das listas, podemos reparar a diferença de tamanho em bytes alocados em memória.
Na primeira linha do retorno temos o valor referente a lista_manual, na
segunda linha, o valor em bytes de lista_gerada.
Aqui temos um exemplo bastante básico, porém já é possível notar que a mesma lista, quando criada via gerador, tem tamanho menor. Raciocine em grandes volumes de dados o quanto esta proporção de tamanho faria a diferença no impacto de performance de seu programa.
Apenas como exemplo, também, note o tamanho em bytes de
listamilhão, criada aqui fora de contexto apenas para demonstrar o tamanho de uma expressão de um valor de milhão.
Novamente, procure raciocinar na proporção do espaço de memória
alocado de acordo com o tamanho do conteúdo da respectiva variável.
Partindo para uma aplicação real, supondo que depende do contexto de
nosso programa, precisássemos de um gerador de números sequenciais.
Para isso criamos a função gera_numero(), sem parâmetros mesmo.
Dentro de seu corpo criamos a variável num inicialmente com valor zerado, na sequência criamos de fato o gerador para num por meio de yield.
Em outros momentos, como de costume, usávamos de return para retornar um determinado dado/valor para uma variável ao final da execução da função, aqui, no contexto de um gerador, return é substituído por yield, por fim num é atualizada com seu valor acrescido de 1.
Fora da função é criada a variável gerador que chama a função
gera_numero( ).
A partir desse momento, temos nosso gerador instanciado e em
execução.
Por meio de nossa função print( ) parametrizada com next( ) por sua vez parametrizada com nosso gerador, a cada execução será gerado um número sequencial, conforme a demanda.
Dessa forma, usaremos apenas o necessário de números e apenas no tempo certo em que o restante do código exigir tal funcionalidade, poupando espaço e processamento.
O retorno, como esperado, foram 5 números sequenciais referentes aos 5
comandos print(next()) usados no exemplo.