Funções e mais funções em Python

Python é uma linguagem de programação realmente incrível, não é mesmo?! Você que anda curioso sobre ela, provavelmente já deve ter lido alguma coisa sobre funções em Python. Que tal vermos juntos algumas coisas legais que podemos fazer com funções? Ainda melhor:  que tal eu te dar um descontão de 40% no curso de Python-Pro se você resolver meu desafio sobre elas?! Ficou interessado? Vamos lá que eu te conto mais!

Definindo funções

Em Python, criamos uma função usando a palavra reservada Def. Por exemplo:

def minha_funcao():
    frase = "Olá mundo!!"
    return frase

Para usarmos essa função, bastaria executar no terminal interativo do Python:

>>> minha_funcao()

Uma coisa importante: observe a indentação do código. Ela é obrigatória em Python! Oficialmente, devemos usar quatro espaços vazios para formar o
bloco de código que fará parte da função. Como você pode ver, uma função é formada por pelo menos três coisas básicas: (1)
a definição do nome da função, o código interno (onde podemos implementar nossos algoritmos!) e a saída da função (o return).

Funções mirabolantes em Python

Bem simples e elegante a criação de funções, não?! A partir disso, podemos fazer coisas mirabolantes com elas. Por exemplo, podemos passar dados
com diferentes graus de complexidade para nossas funções. Para uma entrada de dados simples, poderíamos fazer:

def minha_funcao(nome):
    frase = "Este é um 'Olá mundo' feito por {}!".format(nome)
    return frase

O resultado seria o seguinte:

>>> minha_funcao("Anderson")
"Este é um 'Olá mundo' feito por Anderson!"

Já se quisermos criar uma função que receberá um número arbitrário de dados, seria assim:

def minha_funcao(*nomes):
    print("Este é um 'Olá mundo' feito pela seguinte galera:")
    for nome in nomes:
        print(nome)

    return "Funcionou que uma beleza!"

Ao usarmos essa função, ficaria assim:

>>> minha_funcao("Anderson", "João", "Ana Luiza")
Este é um 'Olá mundo' feito pela seguinte galera:
Anderson
João
Ana Luiza
'Funcionou que uma beleza!'

Mais legal ainda: podemos construir funções dentro de funções. Seria assim:

def converte(numero, unidade):

    def metro(valor):
        return valor / 100

    def centimetro(valor):
        return valor * 100

    if unidade == "metro":
        resultado = "{} centímetro(s)".format(centimetro(numero))
    elif unidade == "centímetro":
        resultado = "{} metro(s)".format(metro(numero))
    else:
        resultado = "Os únicos valores válidos são: 'metro' e 'centímetro'."

    return resultado

Cujo resultado fica assim:

>>> converte(100, "centímetro")
'1.0 metro(s)'

Nesse caso, observe que as funções metro e centimetro só existem dentro do escopo da função converte. Experimente  chamar essas funções isoladamente aí, no seu Python!

Que tal um desafio agora?!

Desafio

Crie uma função que recebe um número inteiro como parâmetro e retorna uma outra função: uma função de exponenciação com o número qualquer fornecido. Isso mesmo, a ideia é implementar uma função que retornará uma outra função! Todos os exemplos que vimos anteriormente retornam um valor simples (um número ou um texto). Entretanto, é possível implementarmos uma função capaz de construir uma outra função e retorná-la  para o usuário!

Ao fim, seu código deverá funcionar da seguinte maneira:

>>> potencia2 = geradorDeFuncao(2)
>>> potencia2(2)
4
>>> potencia2(3)
9
>>> potencia2(10)
100

Nesse exemplo, o usuário empregou a função geradorDeFuncao para gerar um outra função: a função que eleva números ao quadrado  (expoente 2). Note que o usuário deve conseguir gerar funções de potência para qualquer número fornecido à função geradorDeFuncao.

Gostou do desafio?! Você vai gostar mais ainda de saber que se você for o primeiro a conseguir solucionar esse problema e enviá-lo para o meu e-mail
(anderson.eduardo@letscode-academy.com) você terá 40% de desconto no curso no curso de Python-Pro. Nada mal, não é?!

O desafio está lançado! Boa sorte pessoal!!

Resolução

Desafio finalizado, pessoal! E nosso campeão foi… Rafael Fox!! Parabéns Rafael e a tod@s que participaram do desafio!!

 Gabarito

O conceito por traz do desafio proposto é o function closure. Em última análise, esse é um procedimento de armazenar uma função e um ambiente específico para ela, que, aqui, é uma outra função Python (também chamada factory function). Para mais informações, você pode ler esse texto ou, para algo mais formal e clássico, esse artigo. Finalmente, segue o código esperado como resposta do desafio:

def gerar_potencia(p):

    def potencia_n(num):
        return num ** p

    return potencia_n

Que, usando, seria assim:

>>> pot2 = gerar_potencia(2)
>>> type(pot2)
>>> pot2(3)
9
>>> pot2(5)
25
>>> pot2(6)
36

Até o próximo desafio!! : )