Coletânea Python do ZERO às Redes Neurais Artificiais
Recursividade
Na programação, tanto estruturada quanto orientada a objetos, dada a complexidade de nosso programa, boa parte das vezes criamos funções bastante simples e com característica de ser executada apenas uma vez para um determinado fim em nosso programa, sendo em seguida desconsiderada.
Porém, programas mais robustos podem exigir que determinadas funções sejam executadas várias vezes, normalmente até alcançar um objetivo, isto na prática se chama recursividade.
Importante salientar que essa lógica aqui é bastante parecida com algumas estruturas condicionais, porém, trabalhando com recursividade aplicada nossas funções não somente usarão de estruturas condicionais internamente, mas irão se comportar como uma à medida que suas funcionalidades são requisitadas pelo interpretador.
Diferentemente das estruturas for e while, uma função recursiva pode ser reutilizada inúmeras vezes conforme a demanda, assim como possui uma característica padrão de que quando construída, em algum momento ela retornará ela mesma reprocessando os dados.
Pode parecer um pouco confuso, mas raciocine que neste tipo de arquitetura de dado, podemos dentro da função chamar ela mesma para que funcione em loop até uma certa condição ser validade.
Também é uma prática comum uma função recursiva ser criada de modo que gere um retorno atribuído a uma variável externa, pois ao final de sua execução ela pode ser chamada por outra variável, com outros parâmetros, sem interferir nos retornos gerados anteriormente.
Em resumo, em Python é perfeitamente permitido que tenhamos estruturas de dados aninhadas, como listas dentro de listas, dicionários dentro de dicionários, estruturas condicionais dentro de estruturas condicionais, sendo assim por quê não, funções dentro de funções.
Outro ponto a ser considerado é que no processo de criação de todo o bloco de uma função recursiva, parte do código será dedicado a funcionalidade que retornará algum dado/valor para alguma variável, assim como parte do código será dedicado a chamar a própria função em loop, o que para alguns autores é chamado como caso base e caso recursivo, respectivamente.
Desde que respeitadas a sintaxe e a estrutura lógica da função em si, esta prática funcionará perfeitamente.
Partindo para prática, vamos entender a lógica de uma função recursiva sobre um exemplo onde estamos criando uma função que recebe um número e retorna seu fatorial (o mesmo número multiplicado por todos seus antecessores).
Inicialmente declaramos a função fatorial( ) que recebe como parâmetros um num já especificado que deve ser do tipo int e que deve retornar um número do tipo int.
Em seguida é criada uma estrutura condicional onde quando o valor de num for igual a 1, retorne 1. Isso diz respeito a própria lógica de um fatorial, o último expoente pelo qual o número será multiplicado é 1. Note que este bloco é o caso base, onde consta as linhas referentes a funcionalidade da função em si.
Logo após, fora da estrutura condicional existe um retorno bem peculiar, preste bastante atenção na forma com que esse retorno é declarado. Aqui, no chamado caso recursivo, o retorno chama a própria função fatorial( ) onde ele mesmo está inserido.
Nesse retorno o valor atribuído a num é multiplicado pelo fatorial parametrizado com o próprio num subtraindo seu valor final em 1. Em outras palavras, estamos criando uma cadeia de execução em loop, onde essa função será executada novamente se retroalimentando com o valor de num, até que o mesmo seja igual a 1.
Na sequência é criada uma variável de nome fator, que chama a função fatorial( ) parametrizando a mesma com 12.
E como esperado, o resultado fatorial de 12 é 479001600
Código Completo:
Se você quiser realizar o debug do código, perceberá que até o final do processo, a função fatorial( ) será executada 12 vezes.
Lembrando que cada execução dessas estará em um espaço de memória alocado, sendo assim, dependendo o número a ser fatorado o retorno pode ser enorme, proporcional ao número de recursões, inclusive excedendo os limites de memória pré-definidos automaticamente por segurança.
Apenas como exemplo, fatorando o número 500 é gerado um retorno com mais de 1000 dígitos.
Tentando realizar a fatoração de 1000 é gerado um traceback. Esta conta seria possível, mas excede o limite de memória disponível para este tipo de função.
Fatorial de 5.
Sendo assim, apenas como curiosidade, sempre que houver a possibilidade de um retorno extrapolar a memória, ou de uma função recursiva entrar em um loop infinito, será gerado um traceback.
Porém, supondo que você realmente tenha uma aplicação que necessite suporte a números muito grandes, você pode configurar manualmente um limite por meio dos comandos import sys e sys.setrecursionlimit(5000), aqui parametrizado com 5000 apenas como exemplo.
Dessa forma o interpretador do Python irá obedecer a este limite estipulado.