6  Mais Funções

Neste capítulo, vamos aprofundar o estudo de funções depois da base de Funções e Estruturas de Dados.

Ordem deste capítulo: 1. Boas práticas para iniciantes 2. Recursão 3. Assinaturas avançadas 4. Funções lambda 5. Funções não puras


6.1 Boas práticas para iniciantes

Boas práticas tornam o código mais legível e mais fácil de manter.

Exemplos Práticos:

(padroes_entrada_principal.py): Funções pequenas, nomes claros e bloco principal.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa organizado em funções pequenas e com ponto de entrada principal.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Dois números decimais.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função para somar os números.
# 2. Defina uma função para formatar a mensagem final.
# 3. Defina uma função main para coordenar entrada, processamento e saída.
# 4. Execute main apenas no bloco if __name__ == "__main__".
#
# Casos especiais:
# 1. O foco é organização e legibilidade.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def somar(a, b):
    return a + b


def montar_mensagem(resultado):
    return f"Resultado da soma: {resultado}"


def main():
    numero1 = float(input("Digite o primeiro número: "))
    numero2 = float(input("Digite o segundo número: "))

    total = somar(numero1, numero2)
    mensagem = montar_mensagem(total)
    print(mensagem)


if __name__ == "__main__":
    main()

(calculador_frete.py): Exemplo de decomposição com responsabilidades claras.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para calcular o custo de frete usando decomposição em funções.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. O peso do pacote, em kg (decimal).
# 2. A distância da entrega, em km (decimal).
# 3. O tipo de envio ("normal" ou "expresso").
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função para calcular o custo base pelo peso:
#    - até 1 kg: R$ 12,00
#    - acima de 1 kg e até 5 kg: R$ 20,00
#    - acima de 5 kg: R$ 35,00
# 2. Defina uma função para calcular o adicional de distância:
#    - R$ 0,05 por km
# 3. Defina uma função para aplicar fator do tipo de envio:
#    - normal: fator 1.0
#    - expresso: fator 1.5
# 4. Defina uma função principal de cálculo que combine os resultados anteriores.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def calcular_custo_base_por_peso(peso):
    # Retorna o custo base conforme a faixa de peso.
    # Pacotes menores têm custo fixo, maiores têm custo escalonado.
    if peso <= 1:
        return 12.0
    if peso <= 5:
        return 20.0
    return 35.0


def calcular_adicional_distancia(distancia):
    # Calcula o adicional de distância com taxa fixa por km.
    # Multiplica a distância por 0.05 para obter o valor adicional.
    return distancia * 0.05


def aplicar_fator_envio(valor, tipo_envio):
    # Aplica fator multiplicador de acordo com o tipo de envio.
    # "expresso" aumenta o custo em 50%, "normal" mantém o mesmo.
    if tipo_envio == "expresso":
        return valor * 1.5
    return valor


def calcular_frete_total(peso, distancia, tipo_envio):
    # Combina as etapas do cálculo em uma função de alto nível.
    # Passo 1: Calcula o custo base pelo peso.
    base = calcular_custo_base_por_peso(peso)
    # Passo 2: Calcula o adicional por distância.
    adicional = calcular_adicional_distancia(distancia)
    # Passo 3: Soma os custos.
    subtotal = base + adicional
    # Passo 4: Aplica o fator do tipo de envio.
    return aplicar_fator_envio(subtotal, tipo_envio)


peso_pacote = float(input("Digite o peso do pacote (kg): "))
distancia_entrega = float(input("Digite a distância (km): "))
tipo_envio = input("Digite o tipo de envio (normal/expresso): ")

# Calcula o frete total usando a função de alto nível.
total = calcular_frete_total(peso_pacote, distancia_entrega, tipo_envio)

# Exibe o resultado do frete.
print(f"Frete total: R$ {total}")

(refactor_fluxo_principal.py): Refatoração completa com funções pequenas e main.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para refatorar um fluxo em funções separadas.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Nome do aluno (texto).
# 2. Nota do aluno (decimal).
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função para ler os dados.
# 2. Defina uma função para classificar a situação.
# 3. Defina uma função para montar a mensagem final.
# 4. Use uma função main e execute com if __name__ == "__main__".
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def ler_dados():
    nome = input("Nome do aluno: ")
    nota = float(input("Nota do aluno: "))
    return nome, nota


def classificar_nota(nota):
    if nota >= 7:
        return "Aprovado"
    if nota >= 5:
        return "Recuperação"
    return "Reprovado"


def montar_mensagem(nome, nota, situacao):
    return f"Aluno: {nome} | Nota: {nota} | Situação: {situacao}"


def main():
    nome, nota = ler_dados()
    situacao = classificar_nota(nota)
    print(montar_mensagem(nome, nota, situacao))


if __name__ == "__main__":
    main()

6.2 Recursão

Recursão acontece quando uma função chama a si mesma. Ela precisa de caso base para parar.

Exemplos Práticos:

(contagem_recursiva.py): Contagem regressiva recursiva.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para fazer contagem regressiva com recursão.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Um número inteiro inicial maior ou igual a zero.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função recursiva chamada contagem_regressiva.
# 2. A função deve exibir o número atual.
# 3. Se o número atual for maior que zero, a função deve chamar a si mesma com valor - 1.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def contagem_regressiva(numero):
    print(numero)
    if numero > 0:
        contagem_regressiva(numero - 1)


inicio = int(input("Digite um número inteiro não negativo: "))
contagem_regressiva(inicio)

(fatorial_recursivo.py): Cálculo de fatorial com caso base e passo recursivo.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para calcular fatorial com recursão.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Um número inteiro não negativo.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função recursiva fatorial(n).
# 2. Caso base: se n for 0 ou 1, retorne 1.
# 3. Caso recursivo: retorne n * fatorial(n - 1).
# 4. No script principal, leia n, chame a função e exiba o resultado.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def fatorial(n):
    if n <= 1:
        return 1
    return n * fatorial(n - 1)


numero = int(input("Digite um número inteiro não negativo: "))

print(f"Fatorial de {numero}: {fatorial(numero)}")

(rastreador_fatorial_recursivo.py): Exemplo com rastreamento explícito da pilha de chamadas.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para mostrar a pilha de chamadas do fatorial recursivo.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Um inteiro não negativo n.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina fatorial_com_rastro(n) com caso base n <= 1.
# 2. Antes da chamada recursiva, exiba "Entrando em fatorial(n)".
# 3. Após o retorno, exiba "Retornando valor para fatorial(n)".
# 4. Exiba o resultado final no script principal.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def fatorial_com_rastro(n):
    print(f"Entrando em fatorial({n})")
    if n <= 1:
        print(f"Retornando 1 para fatorial({n})")
        return 1

    resultado = n * fatorial_com_rastro(n - 1)
    print(f"Retornando {resultado} para fatorial({n})")
    return resultado


numero = int(input("Digite um inteiro não negativo: "))

print(f"Resultado final: {fatorial_com_rastro(numero)}")

6.3 Assinaturas avançadas

Assinaturas avançadas aumentam a flexibilidade das funções.

Exemplos Práticos:

(assinaturas_avancadas.py): Exemplo com *args, **kwargs e separador posicional /.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para praticar assinaturas avançadas de funções.
# O programa não precisa solicitar entradas do usuário.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função com *args para somar valores posicionais.
# 2. Defina uma função com **kwargs para formatar um relatório de aluno.
# 3. Defina uma função com separador posicional (/) para mostrar chamadas válidas.
# 4. Exiba resultados de chamadas de exemplo no script principal.
#
# Casos especiais:
# 1. O objetivo é entender assinatura e forma de chamada.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def somar_varios(*args):
    return sum(args)


def montar_relatorio(**kwargs):
    nome = kwargs.get("nome", "Sem nome")
    nota = kwargs.get("nota", 0)
    return f"Aluno: {nome} | Nota: {nota}"


def potencia(base, expoente, /):
    return base**expoente


print(somar_varios(2, 3, 5))
print(montar_relatorio(nome="Ana", nota=8.5))
print(potencia(2, 4))

(padroes_entrada_principal.py): Observe como assinaturas simples já melhoram a legibilidade.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa organizado em funções pequenas e com ponto de entrada principal.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Dois números decimais.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função para somar os números.
# 2. Defina uma função para formatar a mensagem final.
# 3. Defina uma função main para coordenar entrada, processamento e saída.
# 4. Execute main apenas no bloco if __name__ == "__main__".
#
# Casos especiais:
# 1. O foco é organização e legibilidade.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def somar(a, b):
    return a + b


def montar_mensagem(resultado):
    return f"Resultado da soma: {resultado}"


def main():
    numero1 = float(input("Digite o primeiro número: "))
    numero2 = float(input("Digite o segundo número: "))

    total = somar(numero1, numero2)
    mensagem = montar_mensagem(total)
    print(mensagem)


if __name__ == "__main__":
    main()

(validador_assinaturas.py): Aplicação prática de *args, **kwargs e /.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para validar uso de *args, **kwargs e / em assinaturas.
# O programa não precisa solicitar entradas do usuário.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função calcular_media(*notas) que retorne a média.
# 2. Defina uma função perfil_aluno(**dados) que monte uma mensagem textual.
# 3. Defina uma função potencia(base, expoente, /) e faça chamadas válidas.
# 4. Exiba os resultados no script principal.
#
# Casos especiais:
# 1. Se não houver notas, retorne 0 na média.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def calcular_media(*notas):
    if not notas:
        return 0
    return sum(notas) / len(notas)


def perfil_aluno(**dados):
    nome = dados.get("nome", "Sem nome")
    curso = dados.get("curso", "Sem curso")
    return f"Aluno: {nome} | Curso: {curso}"


def potencia(base, expoente, /):
    return base**expoente


print(f"Média: {calcular_media(7, 8, 9)}")
print(perfil_aluno(nome="Lia", curso="Engenharia"))
print(f"Potência: {potencia(2, 3)}")

6.4 Funções lambda

A palavra-chave lambda cria funções anônimas (sem nome) em uma única expressão, usando a sintaxe lambda parâmetros: expressão. Diferente de def, ela não precisa de nome nem de return, pois o resultado da expressão é retornado automaticamente. É útil para casos pontuais em que definir uma função completa seria desnecessário, como em argumentos key de sorted(), min() e max().

Exemplos Práticos:

(funcoes_anonimas.py): Lambda simples e lambda em ordenação com key.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para usar funções lambda em casos curtos.
# O programa não precisa solicitar entradas do usuário.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma lambda para dobrar um número.
# 2. Use lambda em sorted com key para ordenar uma lista de tuplas pela segunda posição.
# 3. Exiba os resultados.
#
# Casos especiais:
# 1. Prefira lambda apenas para expressões simples e legíveis.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

dobro = lambda x: x * 2

pares = [("Ana", 8.0), ("Bruno", 6.5), ("Carla", 9.2)]
ordenados = sorted(pares, key=lambda item: item[1], reverse=True)

print(dobro(7))
print(ordenados)

(contador_frequencia_palavras.py): Compare com um problema resolvido sem lambda e analise legibilidade.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para contar a frequencia de palavras em um relatorio.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. Uma observacao de inspeção em texto.
#
# O programa deve funcionar da seguinte maneira:
# 1. Converta o texto para letras minusculas.
# 2. Separe as palavras por espaco usando split.
# 3. Use um dicionario para contar quantas vezes cada palavra aparece.
# 4. Exiba o dicionario final de frequencias.
#
# Casos especiais:
# 1. Ignore espacos extras no inicio e no fim do texto.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# strip remove espacos extras das extremidades.
relatorio = input("Digite a observacao de inspecao: ").strip().lower()
palavras = relatorio.split()

# O dicionario guardara a contagem de cada palavra.
# Aqui vemos um padrao muito comum: usar a propria palavra como chave
# e usar o numero de ocorrencias como valor.
# Sempre que a palavra reaparece, seu valor no dicionario e atualizado.
frequencia = {}

# Cada palavra encontrada atualiza sua contagem no dicionario.
# get(palavra, 0) significa: pegue a contagem atual da palavra;
# se ela ainda nao existir, comece em 0.
for palavra in palavras:
    frequencia[palavra] = frequencia.get(palavra, 0) + 1

print(f"Frequencia de palavras: {frequencia}")

(ordenador_com_lambda.py): Ordenação crescente e decrescente com lambda.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para ordenar dados com lambda e comparar resultados.
# O programa não precisa solicitar entradas do usuário.
#
# O programa deve funcionar da seguinte maneira:
# 1. Use a lista fixa [("A", 3), ("B", 1), ("C", 2)].
# 2. Ordene por segundo elemento em ordem crescente com key=lambda.
# 3. Ordene por segundo elemento em ordem decrescente com reverse=True.
# 4. Exiba as duas listas ordenadas.
#
# Casos especiais:
# 1. O objetivo é entender leitura de lambda em contexto curto.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

dados = [("A", 3), ("B", 1), ("C", 2)]

ordenado_crescente = sorted(dados, key=lambda item: item[1])
ordenado_decrescente = sorted(dados, key=lambda item: item[1], reverse=True)

print(f"Original: {dados}")
print(f"Crescente: {ordenado_crescente}")
print(f"Decrescente: {ordenado_decrescente}")

6.5 Funções não puras

No capítulo anterior sobre funções, priorizamos uma abordagem importante: sempre que possível, a função deve receber dados por parâmetros e retornar resultados com return. A entrada com input() e a saída com print() ficam no script principal. Funções que sempre retornam o mesmo resultado para os mesmos argumentos e não causam efeitos colaterais são conhecidas como funções puras.

funções não puras têm efeitos colaterais, como leitura de input(), uso de print() interno ou modificação de variáveis externas. Elas são práticas em alguns cenários, mas podem dificultar testes e reutilização.

Exemplos Práticos:

(comparacao_puro_impuro.py): Comparação direta entre função pura e não pura para a mesma tarefa.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para comparar funções puras e não puras.
# O programa deve solicitar ao usuário as seguintes informações:
# 1. Dois números inteiros.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma função pura que receba dois números e retorne a soma.
# 2. Defina uma função não pura que faça input e print internamente para somar números.
# 3. Execute as duas abordagens e compare os resultados.
#
# Casos especiais:
# 1. O objetivo é didático: observar legibilidade, teste e manutenção.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

def soma_pura(a, b):
    return a + b


def soma_nao_pura():
    valor1 = int(input("(Não pura) Digite o primeiro número: "))
    valor2 = int(input("(Não pura) Digite o segundo número: "))
    print(f"(Não pura) Soma: {valor1 + valor2}")


numero1 = int(input("Digite o primeiro número: "))
numero2 = int(input("Digite o segundo número: "))

resultado = soma_pura(numero1, numero2)
print(f"(Pura) Soma: {resultado}")

soma_nao_pura()

(eco.py): Exemplo clássico de interação direta com usuário.

# Exibe a pergunta, pausa o programa e espera o usuário digitar.

# O que for digitado é salvo em "nome_usuario"
nome_usuario = input("Qual é o seu nome? ")

# Usa o valor capturado para cumprimentar o usuário

print(f"Bem-vindo ao sistema, {nome_usuario}.")

(decisor_pureza_funcoes.py): Decisão guiada entre abordagem pura e não pura em cenários reais.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para decidir entre função pura e função não pura em cenários reais.
# O programa não precisa solicitar entradas do usuário.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina três cenários práticos de programação.
# 2. Para cada cenário, indique se a melhor escolha é função pura ou não pura.
# 3. Exiba uma justificativa curta para cada escolha.
#
# Casos especiais:
# 1. O objetivo é consolidar decisão de design, não cálculo numérico.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

escolhas = {
    "Cálculo de média para vários sistemas": (
        "Função pura",
        "Facilita testes e reutilização.",
    ),
    "Perguntar nome e cumprimentar no terminal": (
        "Função não pura",
        "Interação direta com usuário é o objetivo.",
    ),
    "Regra de desconto que muda com frequência": (
        "Função pura",
        "Isola regra de negócio e reduz acoplamento.",
    ),
}

for cenario, (tipo, motivo) in escolhas.items():
    print(f"Cenário: {cenario}")
    print(f"Escolha: {tipo}")
    print(f"Justificativa: {motivo}")
    print("-")