Capítulo 5: Funções e Modularização#

Cinco árvores de traços em que a cor de cada galho indica a profundidade da recursão que o desenhou Cinco árvores de traços em que a cor de cada galho indica a profundidade da recursão que o desenhou

Definindo Funções#

Uma função é um bloco de código com nome, que executa uma tarefa e pode ser chamado quantas vezes for preciso. Em vez de repetir as mesmas linhas em vários pontos do programa, escreve-se a tarefa uma vez, dentro da função, e chama-se a função onde ela for necessária. Isso deixa o programa mais curto, mais fácil de ler e mais fácil de corrigir: um erro na tarefa se conserta num lugar só. Dividir um programa em funções, cada uma com uma tarefa, é o que se chama de modularização.

Para definir uma função, utilizamos a palavra reservada def. A estrutura básica de uma função inclui:

  • Palavra-chave def: Indica o início da definição de uma função.

  • Nome da função: Deve ser descritivo e seguir as convenções de nomenclatura de Python, conforme descrito na PEP 8. Use letras minúsculas com palavras separadas por underscores (convenção snake_case).

  • Parênteses () contendo os parâmetros (se houver) separados por vírgulas.

  • Dois pontos : para terminar a linha de cabeçalho.

  • Bloco de código indentado que constitui o corpo da função.

  • (Opcional) return para retornar um valor da função. Se omitido, a função retorna None por padrão.

Exemplo simples de uma função para calcular e retornar a soma de dois números.

A animação a seguir mostra o caminho dos dados nesse exemplo. Os valores 3 e 4 entram na função pelos parâmetros, a conta acontece dentro dela, e o return devolve o 7 para o programa. O que está ativo acende ao mesmo tempo no código e no desenho, e a cor indica a fase: azul na entrada, roxo dentro da função e laranja na saída.

Animação em dezenove quadros do primeiro exemplo do capítulo, a função soma2, chamada com soma2 de 3 e 4. À esquerda, o código, com uma seta na linha que está rodando, e a saída. À direita, a função desenhada como uma caixa com duas entradas no alto, uma para parametro1 e outra para parametro2, e uma saída embaixo, marcada return, que desce até a caixa do programa, onde fica a variável resultado. Os valores 3 e 4 saem da chamada e entram pelas duas entradas. Dentro da caixa, a conta parametro1 mais parametro2 vira 3 mais 4 e dá 7. O 7 sai pelo return até o resultado do programa, e o print o mostra na saída. O que está ativo acende ao mesmo tempo no código e no desenho, em azul na entrada, em roxo dentro da função e em laranja na saída Animação em dezenove quadros do primeiro exemplo do capítulo, a função soma2, chamada com soma2 de 3 e 4. À esquerda, o código, com uma seta na linha que está rodando, e a saída. À direita, a função desenhada como uma caixa com duas entradas no alto, uma para parametro1 e outra para parametro2, e uma saída embaixo, marcada return, que desce até a caixa do programa, onde fica a variável resultado. Os valores 3 e 4 saem da chamada e entram pelas duas entradas. Dentro da caixa, a conta parametro1 mais parametro2 vira 3 mais 4 e dá 7. O 7 sai pelo return até o resultado do programa, e o print o mostra na saída. O que está ativo acende ao mesmo tempo no código e no desenho, em azul na entrada, em roxo dentro da função e em laranja na saída

O mesmo código, para rodar na página:

def soma2(parametro1, parametro2):
    total = parametro1 + parametro2
    return total

# Chamando a função
resultado = soma2(3, 4)
print(resultado)

Saída do Código:

7

Repare na ordem em que esse código roda. O def só cria a função soma2: nenhuma linha do corpo é executada nesse momento. O corpo roda quando a função é chamada, na linha resultado = soma2(3, 4), e roda de novo a cada nova chamada.

Os nomes entre parênteses na definição, parametro1 e parametro2, são os parâmetros: variáveis da função que recebem valores a cada chamada. Os valores escritos na chamada, 3 e 4, são os argumentos. Eles são entregues na ordem em que aparecem: o primeiro argumento vai para o primeiro parâmetro e o segundo para o segundo. Dentro da função, então, parametro1 vale 3 e parametro2 vale 4.

A última linha do corpo, return total, é o retorno da função: ela devolve o valor de total a quem fez a chamada. Por isso a chamada soma2(3, 4) vale 7, e é esse 7 que a atribuição guarda em resultado. Nem toda função devolve um valor: as seções “Funções sem Retorno” e “Funções com Retorno”, mais adiante, tratam dos dois casos.

Docstrings e Type Hints#

Duas práticas deixam uma função mais fácil de entender e de usar: docstrings e type hints.

  • Docstrings são strings de documentação escritas logo abaixo da linha do def. Elas dizem o que a função faz, o que cada parâmetro recebe e o que ela devolve, e aparecem quando se chama help() com a função.

  • Type hints (ou anotações de tipo) indicam o tipo esperado de cada parâmetro e do valor de retorno. Eles ajudam quem lê o código e as ferramentas que verificam tipos.

    Exemplo de docstring e type hints:

    def soma(a: float, b: float) -> float:
        """
        Calcula a soma de dois números.
    
        Args:
            a (float): Primeiro número.
            b (float): Segundo número.
        
        Returns:
            float: A soma de 'a' e 'b'.
        """
        return a + b
    

    Neste exemplo, a e b devem ser float, e soma devolve um float. Chamar help(soma) mostra a docstring.

    O Python, porém, não confere nem converte esses tipos ao executar: soma(2, 3) devolve 5, um int, e até soma("Py", "thon") roda e devolve 'Python'. As anotações servem a quem lê o código e a ferramentas como o mypy, que verificam os tipos sem executar o programa.

Funções sem Retorno (Procedimentos)#

Uma função sem return, também chamada de procedimento, executa uma ação, como mostrar algo na tela, e não devolve valor nenhum (devolve None, implicitamente).

Exemplo:

def imprimir_cabecalho(titulo: str) -> None:
    """
    Imprime um cabeçalho formatado com bordas.

    Args:
        titulo: Texto a ser exibido no centro do cabeçalho.
    """
    largura = len(titulo) + 6
    print("=" * largura)
    print(f"|  {titulo}  |")
    print("=" * largura)

# Chamando a função
imprimir_cabecalho("RELATÓRIO MENSAL")

Saída do Código:

======================
|  RELATÓRIO MENSAL  |
======================

Repare que o type hint -> None deixa explícito que esta função existe pelo seu efeito colateral (imprimir na tela), não para devolver um valor, o que é típico de um procedimento.

Funções com Retorno#

A soma2, do começo do capítulo, já é uma função com retorno: ela devolve um valor a quem a chamou, que pode guardá-lo numa variável ou usá-lo numa conta. A seguir, mais dois exemplos, um sem valor padrão e outro com valor padrão.

Exemplo sem Valor Padrão:

def soma(a: float, b: float) -> float:
    """
    Calcula a soma de dois números.
    
    Args:
        a: Primeiro número a ser somado.
        b: Segundo número a ser somado.
    
    Returns:
        A soma dos valores 'a' e 'b'.
    """
    return a + b

# Chamando a função com argumentos
resultado = soma(3.5, 5.0)
print(f"Resultado: {resultado}")

Saída do Código:

Resultado: 8.5

Exemplo com Valor Padrão:

def somaP(a: float, b: float = 0.0) -> float:
    """
    Calcula a soma de dois números.

    Args:
        a: Primeiro número a ser somado.
        b: Segundo número a ser somado, padrão é 0.0.
    
    Returns:
        A soma dos valores 'a' e 'b'.
    """
    return a + b

# Chamando a função com diferentes argumentos
resultado1 = somaP(3.5, 5.0)  # Dois argumentos
resultado2 = somaP(3.5)       # Usando o valor padrão de b
print(f"Resultados: {resultado1}, {resultado2}")

Saída do Código:

Resultados: 8.5, 3.5

No segundo exemplo, b tem o valor padrão 0.0. Um parâmetro com valor padrão é opcional na chamada: quando o argumento é omitido, como em somaP(3.5), o parâmetro assume o valor escrito na definição.

O return encerra a função#

Quando um return é executado, a função termina ali e devolve o valor; as linhas abaixo dele não rodam. Isso permite escrever uma decisão como uma sequência de testes, cada um com o seu return:

def situacao(nota: float) -> str:
    if nota >= 7:
        return "aprovado"
    if nota >= 5:
        return "recuperação"
    return "reprovado"

print(situacao(8.5))
print(situacao(6.0))
print(situacao(3.0))

Saída do Código:

aprovado
recuperação
reprovado

Com a nota 8.5, o primeiro return já encerra a função, e o segundo teste nem chega a ser avaliado. O último return só é alcançado quando nenhum dos testes anteriores deu certo, por isso não é preciso elif nem else. O mesmo vale dentro de um laço: um return dentro de um for encerra o laço e a função de uma vez.

Argumentos Posicionais e Nomeados#

Na chamada, os argumentos podem ser passados de duas formas: posicionais e nomeados.

  • Argumentos posicionais: vão para os parâmetros na ordem em que aparecem.

    Exemplo:

    def exibir_info(nome: str, idade: int) -> None:
        print(f"Nome: {nome}, Idade: {idade}")
    
    # Chamando a função com argumentos posicionais
    exibir_info("Alice", 30)
    

    Saída do Código:

    Nome: Alice, Idade: 30
    
  • Argumentos nomeados: levam o nome do parâmetro, na forma nome=valor, e por isso a ordem não importa.

    Exemplo:

    def exibir_info(nome: str, idade: int) -> None:
        print(f"Nome: {nome}, Idade: {idade}")
    
    # Chamando a função com argumentos nomeados
    exibir_info(idade=30, nome="Alice")
    

    Saída do Código:

    Nome: Alice, Idade: 30
    

As duas formas podem ser misturadas, desde que os argumentos posicionais venham primeiro: exibir_info("Alice", idade=30) funciona, e exibir_info(nome="Alice", 30) é um erro de sintaxe.

Escopo: variáveis locais e globais#

Uma variável criada dentro de uma função é local: ela existe só enquanto a função roda e só pode ser usada dentro dela. Os parâmetros também são variáveis locais. Uma variável criada fora de qualquer função é global.

def calcular_media(nota1: float, nota2: float) -> float:
    media = (nota1 + nota2) / 2
    return media

resultado = calcular_media(8.0, 6.0)
print(resultado)

Saída do Código:

7.0

A variável media só existe durante a chamada; quando a função termina, só o seu valor sai, pelo return. Um print(media) fora da função pararia o programa com um NameError, porque fora dela esse nome não existe. Para usar a média fora da função, usa-se o valor devolvido, como faz resultado.

Já uma variável global pode ser lida dentro da função:

taxa = 0.25

def preco_com_taxa(preco: float) -> float:
    return preco * (1 + taxa)

print(preco_com_taxa(50.0))

Saída do Código:

62.5

Atribuir um valor a um nome dentro da função, porém, cria uma variável local nova, mesmo que exista uma global com o mesmo nome, e a global não muda:

taxa = 0.25

def mudar_taxa() -> None:
    taxa = 0.10  # cria uma taxa local; a global fica como estava
    print("dentro:", taxa)

mudar_taxa()
print("fora:", taxa)

Saída do Código:

dentro: 0.1
fora: 0.25

Para alterar a variável global de dentro da função, declara-se o nome com a palavra-chave global antes de usá-lo:

pontos = 0

def marcar_ponto() -> None:
    global pontos  # a atribuição abaixo vale para a pontos global
    pontos += 1

marcar_ponto()
marcar_ponto()
print(pontos)

Saída do Código:

2

Sem a linha global pontos, o pontos += 1 pararia o programa com um UnboundLocalError: a atribuição faz de pontos um nome local da função, e o Python tenta ler esse local antes de ele ter valor.

O global funciona, mas a função passa a depender de um nome de fora dela, e cada chamada muda o estado do programa. Na maioria dos casos é mais claro a função receber o valor como parâmetro e devolver o novo com return:

def marcar_ponto(atual: int) -> int:
    return atual + 1

pontos = 0
pontos = marcar_ponto(pontos)
pontos = marcar_ponto(pontos)
print(pontos)

Saída do Código:

2

Funções com Múltiplos Valores de Retorno#

Uma função pode devolver mais de um valor numa única chamada, separando-os por vírgula no return.

Exemplo: Retornando Dois Valores

No exemplo abaixo, soma_dif_valores devolve a soma e a diferença de dois números.

def soma_dif_valores(a: float, b: float) -> tuple[float, float]:
    """
    Calcula a soma e a diferença de dois números.

    Args:
        a: Primeiro número.
        b: Segundo número.

    Returns:
        Uma tupla contendo a soma e a diferença dos números.
    """
    soma_resultado = a + b
    diferenca_resultado = a - b
    return soma_resultado, diferenca_resultado

# Chamando a função
soma_valores, diferenca_valores = soma_dif_valores(10, 5)
print(f"A soma é: {soma_valores}")
print(f"A diferença é: {diferenca_valores}")

Saída do Código:

A soma é: 15
A diferença é: 5

Os resultados saem como 15 e 5, sem casa decimal: os argumentos 10 e 5 são inteiros, e a anotação float não os converte.

Os valores voltam juntos numa tupla, e a chamada pode “desempacotá-los” em variáveis separadas, como em soma_valores, diferenca_valores = soma_dif_valores(10, 5).

Exemplo: Retornando Valores de Tipos Diferentes

A seguir, a função analise_lista retorna a soma dos elementos de uma lista e um valor booleano indicando se todos os números são positivos.

def analise_lista(numeros: list[int]) -> tuple[int, bool]:
    """
    Analisa uma lista de números inteiros.

    Args:
        numeros: Lista de números inteiros.

    Returns:
        - A soma dos elementos.
        - Um valor booleano indicando se todos os números são positivos.
    """
    soma_total = sum(numeros)
    
    # Verificando se todos os números são positivos sem usar all
    todos_positivos = True
    for num in numeros:
        if num <= 0:
            todos_positivos = False
            break

    return soma_total, todos_positivos

# Chamando a função
soma, positivos = analise_lista([3, -1, 5, 7, 2])
print(f"Soma dos elementos: {soma}")
print(f"Todos os elementos são positivos? {positivos}")

Saída do Código:

Soma dos elementos: 16
Todos os elementos são positivos? False

A função retorna a soma dos elementos e verifica se todos são positivos, retornando False caso algum não seja.

Funções que Recebem Outras Funções como Argumentos#

Em Python, uma função pode ser passada como argumento para outra, como qualquer outro valor.

Exemplo: Passando Funções como Argumentos

O exemplo abaixo mostra uma função que aplica uma operação, definida por outra função, a cada elemento de uma lista.

def aplicar_operacao(operacao, lista: list[int]) -> list[int]:
    """
    Aplica uma operação a cada elemento de uma lista.

    Args:
        operacao: Função que define a operação a ser aplicada.
        lista: Lista de elementos.

    Returns:
        Uma nova lista com os resultados da operação aplicada.
    """
    return [operacao(elemento) for elemento in lista]

# Funções para operações matemáticas
def quadrado(x: int) -> int:
    return x * x

def cubo(x: int) -> int:
    return x * x * x

# Aplicando a função quadrado a uma lista
numeros = [1, 2, 3, 4, 5]
resultado_quadrado = aplicar_operacao(quadrado, numeros)
print(f"O quadrado de cada número na lista é: {resultado_quadrado}")

# Aplicando a função cubo a uma lista
resultado_cubo = aplicar_operacao(cubo, numeros)
print(f"O cubo de cada número na lista é: {resultado_cubo}")

Saída do Código:

O quadrado de cada número na lista é: [1, 4, 9, 16, 25]
O cubo de cada número na lista é: [1, 8, 27, 64, 125]
  • A função aplicar_operacao recebe:

    • Uma função (operacao) que define a lógica de transformação.

    • Uma lista (lista) cujos elementos serão processados.

  • Usamos uma compreensão de lista para aplicar a operação a cada elemento da lista.

  • As funções quadrado e cubo são exemplos de operações que podem ser passadas para aplicar_operacao.

Assim, aplicar_operacao serve para qualquer operação que receba um número e devolva outro.

Funções Anônimas (Lambda)#

Uma função anônima, ou lambda, é uma função curta, sem nome, escrita numa linha só: a palavra-chave lambda, os parâmetros, dois pontos (:) e a expressão que ela devolve.

A sintaxe básica de uma função lambda é:

lambda parâmetros: expressão

Exemplo com um parâmetro#

print((lambda x: x * 2)(3))

Saída do Código:

6

Neste exemplo, uma função lambda que dobra um número é definida e imediatamente chamada com o valor 3. A função calcula 3 * 2 e retorna 6, que o print mostra na tela.

Exemplo com dois parâmetros#

soma = lambda x, y: x + y
print(f"A soma de 5 e 7 é: {soma(5, 7)}")

Saída do Código:

A soma de 5 e 7 é: 12

Aqui, a lambda recebe dois parâmetros, x e y, e devolve a soma. Atribuída à variável soma, ela é chamada como uma função comum. O guia de estilo PEP 8 recomenda usar def quando a função vai ganhar um nome; o exemplo faz assim só para mostrar a sintaxe.

Exemplo com listas e map()#

numeros = [1, 2, 3, 4, 5]
quadrados = list(map(lambda x: x ** 2, numeros))
print(f"Os quadrados dos números {numeros} são: {quadrados}")

Saída do Código:

Os quadrados dos números [1, 2, 3, 4, 5] são: [1, 4, 9, 16, 25]

A função map() aplica a lambda lambda x: x ** 2 a cada elemento de numeros e devolve um objeto map, que o list() transforma em lista.

Exemplo com múltiplos retornos#

quadrado_e_cubo = lambda x: (x ** 2, x ** 3)
resultado = quadrado_e_cubo(5)
print(f"O quadrado e o cubo de 5 são: {resultado}")

Saída do Código:

O quadrado e o cubo de 5 são: (25, 125)

A lambda devolve dois valores numa tupla: o quadrado e o cubo de x. Com o argumento 5, ela devolve (25, 125).

As lambdas servem para funções curtas, de uma expressão só, usadas uma vez, em geral como argumento de outra função, como map() e filter(). A reduce(), que também recebe uma função, não é built-in: fica no módulo functools e precisa de from functools import reduce. Para algo maior que uma expressão, o def é mais legível.

Funções Recursivas#

A recursividade é uma técnica em que uma função chama a si mesma para resolver uma versão menor do mesmo problema. A cada nova chamada, o problema é reduzido a um caso mais simples, até atingir uma situação cuja resposta é conhecida de forma direta. Esse momento de parada é chamado de caso base (ou condição de parada). Sem ele, a função continuaria a se chamar indefinidamente e o programa travaria por estouro de pilha.

Toda função recursiva combina, portanto, duas partes:

  • Caso base: condição que encerra a recursão sem fazer nova chamada.

  • Caso recursivo: chamada à própria função com uma entrada menor.

Ela serve bem a problemas que contêm versões menores de si mesmos, como fatorial, Fibonacci, percursos em árvores e processamento de listas, e costuma dar um código mais curto que a versão com laço.

Exemplo: Soma de Números de 1 até N#

A soma dos números de 1 até um número n pode ser calculada recursivamente:

def soma(n: int) -> int:
    """
    Soma os inteiros positivos de 1 até n.

    Args:
        n: Limite superior da soma.

    Returns:
        A soma 1 + 2 + ... + n. Para n <= 0, retorna 0.
    """
    if n <= 0:                # Proteção: evita recursão infinita para valores não positivos
        return 0
    if n == 1:                # Caso base
        return 1
    return n + soma(n - 1)    # Caso recursivo

# Teste da função
print(soma(5))

Saída do Código:

15

A função soma(5) chama soma(4), que chama soma(3), e assim por diante, até atingir soma(1), que retorna 1. Os valores são somados de volta ao longo da cadeia de chamadas.

Representação Recursiva

A execução da função pode ser visualizada como uma pilha de chamadas. No vídeo a seguir, que você pode pausar a qualquer momento, cada chamada é um degrau que espera a resposta do degrau de baixo. Em cada chamada, a seta do código passa primeiro pelos dois if, e ao lado de cada um aparece a condição calculada com o valor de n, como 5 <= 0 → False. Na descida, em azul, soma(5) chama soma(4), e assim por diante, até soma(1). É o caso base, em roxo, que responde 1 sem fazer nova chamada. Na volta, em laranja, cada resposta sobe um degrau e é somada ali: 1, 3, 6, 10 e 15. O degrau que já respondeu apaga, e a pilha se esvazia.

Resolução acumulada na volta:

soma(1) = 1
soma(2) = 2 + soma(1) = 2 + 1 = 3
soma(3) = 3 + soma(2) = 3 + 3 = 6
soma(4) = 4 + soma(3) = 4 + 6 = 10
soma(5) = 5 + soma(4) = 5 + 10 = 15

Atenção - Limite de recursão

Cada chamada recursiva consome um espaço na pilha de execução do Python. O interpretador limita essa pilha a cerca de 1000 chamadas por padrão. Para entradas pequenas como soma(5), isso não é problema, mas tentar soma(2000) resulta em RecursionError. Para problemas muito grandes, prefira uma versão iterativa equivalente.

Exemplo: Soma de Números em um Intervalo#

Para calcular a soma dos números entre a e b:

def soma_intervalo(a: int, b: int) -> int:
    """
    Soma os inteiros do intervalo [a, b], inclusive nas duas pontas.

    Args:
        a: Limite inferior do intervalo.
        b: Limite superior do intervalo.

    Returns:
        A soma a + (a+1) + ... + b. Retorna 0 se a > b.
    """
    if a > b:                              # Proteção: intervalo inválido
        return 0
    if a == b:                             # Caso base: chegamos ao limite superior
        return a
    return a + soma_intervalo(a + 1, b)    # Caso recursivo

# Teste da função
print(soma_intervalo(3, 7))

Saída do Código:

25

A cada chamada, a aumenta 1. A recursão para quando a chega a b, o caso base, que devolve o próprio a.

Exemplo: Fatorial de um Número#

O fatorial de um número n, denotado n!, é o produto de todos os inteiros positivos de 1 até n. Ele conta, por exemplo, de quantas ordens diferentes 5 livros distintos podem ser arrumados numa prateleira: há 5 opções para o primeiro livro, 4 para o segundo, 3 para o terceiro, e assim por diante, e 5! = 5 × 4 × 3 × 2 × 1 = 120. Depois de escolher o primeiro livro, sobra o mesmo problema com um livro a menos, e é isso que a recursão aproveita: 5! = 5 × 4!.

def fatorial(n: int) -> int:
    """
    Calcula o fatorial de n (n!).

    Args:
        n: Inteiro não negativo.

    Returns:
        O produto 1 × 2 × ... × n. Por convenção, 0! = 1.
        Retorna 0 para entradas negativas (valor inválido).
    """
    if n < 0:                        # Proteção: fatorial não é definido para negativos
        return 0
    if n == 0 or n == 1:             # Caso base
        return 1
    return n * fatorial(n - 1)       # Caso recursivo

# Quantas formas existem de organizar 5 livros distintos em uma prateleira?
print(fatorial(5))

Saída do Código:

120

A função chama a si mesma com n - 1 até que n seja 0 ou 1, retornando 1. No nosso exemplo, fatorial(5) calcula 5 × fatorial(4) = 5 × 24 = 120, ou seja, existem 120 formas distintas de organizar os 5 livros na prateleira.

Nota - Por que 0! é igual a 1

Por convenção matemática, o fatorial de 0 vale 1 (e não 0). Essa definição mantém a recorrência n! = n × (n-1)! consistente para n = 1, já que 1! = 1 × 0! = 1 × 1 = 1. Ela também aparece naturalmente em fórmulas de combinatória, como o número de formas de organizar zero objetos, que é uma única forma (a vazia).

Exemplo: Soma dos Elementos de uma Lista#

A soma dos elementos de uma lista pode ser feita de forma recursiva:

def soma_lista(lista: list[int]) -> int:
    """
    Soma todos os elementos de uma lista de inteiros recursivamente.

    Args:
        lista: Lista de números inteiros.

    Returns:
        A soma dos elementos. Para uma lista vazia, retorna 0.
    """
    if len(lista) == 0:                          # Caso base: lista vazia
        return 0
    return lista[0] + soma_lista(lista[1:])      # Caso recursivo

# Teste da função
lista_exemplo = [1, 2, 3, 4, 5]
print(f"A soma dos números na lista é: {soma_lista(lista_exemplo)}")

Saída do Código:

A soma dos números na lista é: 15

A função pega o primeiro elemento da lista e chama a si mesma com o restante. O processo continua até que a lista esteja vazia.

Dica - Custo do slicing em recursão

A expressão lista[1:] cria uma nova lista a cada chamada recursiva. Para uma lista de tamanho n, isso resulta em n cópias parciais ao longo da recursão, totalizando O(n²) em memória e tempo. Para listas grandes, prefira passar um índice como argumento adicional e percorrer a lista por posição, em vez de fatiar.

Vantagens e Desvantagens da Recursividade#

Vantagens

Desvantagens

Código mais curto e expressivo, próximo da definição matemática do problema.

Cada chamada consome memória na pilha de execução (parâmetros, variáveis locais e endereço de retorno).

Ideal para problemas com estrutura naturalmente recursiva: fatorial, Fibonacci, percursos em árvores e processamento de listas encadeadas.

Python limita a profundidade de chamadas a cerca de 1000 por padrão (sys.setrecursionlimit()). Ultrapassar esse limite levanta RecursionError: maximum recursion depth exceeded.

Evita variáveis auxiliares e laços quando a solução é definida de forma autorreferente.

Sem um caso base bem definido, ocorre estouro de pilha por chamadas que nunca terminam.

Para entradas grandes, em que o número de chamadas se aproxima do limite de profundidade da pilha, uma versão iterativa do mesmo algoritmo costuma ser mais segura. Compare o fatorial recursivo já apresentado com sua versão iterativa equivalente:

def fatorial_iterativo(n: int) -> int:
    """Versão iterativa do fatorial, sem recursão."""
    if n < 0:
        return 0
    resultado = 1
    for i in range(2, n + 1):
        resultado *= i
    return resultado

print(fatorial_iterativo(5))

Saída do Código:

120

A versão iterativa usa apenas uma variável (resultado) e um laço, sem empilhar chamadas. Para entradas pequenas, as duas versões são intercambiáveis. Para valores grandes (por exemplo, n = 10000), a versão recursiva levanta RecursionError, enquanto a iterativa termina normalmente.

Outras Funções Built-in#

Os capítulos anteriores já usaram várias funções built-in, que vêm prontas no Python, sem import: print(), input(), int(), float(), str(), len() e range(). A tabela traz outras que ainda não apareceram.

Função

O que faz

max()

Devolve o maior valor de uma sequência.
max([2, 8, 5]) ➞ 8

min()

Devolve o menor valor de uma sequência.
min([2, 8, 5]) ➞ 2

sum()

Soma os elementos de uma sequência de números.
sum([1, 2, 3]) ➞ 6

sorted()

Devolve uma lista ordenada.
sorted([3, 1, 2]) ➞ [1, 2, 3]

all()

Devolve True se todos os elementos forem verdadeiros.
all([True, True, False]) ➞ False

enumerate()

Gera pares (índice, valor) ao percorrer uma sequência.
list(enumerate(['a', 'b'])) ➞ [(0, 'a'), (1, 'b')]

zip()

Junta os elementos de várias sequências em tuplas.
list(zip([1, 2], ['a', 'b'])) ➞ [(1, 'a'), (2, 'b')]

hash()

Devolve um inteiro calculado a partir do objeto. Para strings, ele muda a cada execução do programa.
hash("texto") ➞ um inteiro

help()

Mostra a documentação de um objeto.
help(print) ➞ a documentação do print

dir()

Lista os atributos e métodos de um objeto.
dir("texto") ➞ os métodos da string

open()

Abre um arquivo e devolve um objeto de arquivo.
open('arquivo.txt', 'r') ➞ um objeto de arquivo

eval()

Calcula uma expressão escrita numa string.
eval("2 + 2") ➞ 4

exec()

Executa um código Python escrito numa string.
exec("x = 5") ➞ cria a variável x


Atenção - Risco de eval e exec

eval() e exec() executam código Python contido em uma string. Isso é perigoso quando a string vem de uma fonte externa (entrada do usuário, arquivo, requisição de rede), pois abre brecha para execução de código arbitrário. Por exemplo, eval(input()) permite que qualquer pessoa rode qualquer comando dentro do seu programa.

Para conversões simples de texto em número, use int(), float() ou bool(). Para avaliar literais Python de forma segura (listas, dicionários, tuplas, números), use ast.literal_eval() do módulo ast. Evite eval e exec em código real, exceto em casos muito específicos em que você controla completamente a entrada.

Observação - módulo math

Funções como math.sqrt() e math.factorial() não são built-in: ficam no módulo math, que precisa ser importado com import math.

Exemplo:

import math
raiz = math.sqrt(16)
print(raiz)

Saída do Código:

4.0

Documentação oficial

Para a lista completa de funções built-in e detalhes de cada uma, consulte a documentação oficial do Python.

Organizando um Programa em Funções#

Até aqui, cada exemplo mostrou uma função sozinha. Num programa de verdade, elas trabalham juntas: cada uma faz uma parte da tarefa, e o programa principal só as chama na ordem certa. É o que a abertura do capítulo chamou de modularização.

O programa abaixo recebe duas notas, calcula a média e mostra a situação do aluno, tudo num bloco só:

nota1 = 8.0
nota2 = 6.0
media = (nota1 + nota2) / 2
if media >= 7:
    situacao = "aprovado"
elif media >= 5:
    situacao = "recuperação"
else:
    situacao = "reprovado"
print(f"Média: {media:.1f} ({situacao})")

Saída do Código:

Média: 7.0 (aprovado)

O mesmo programa, organizado em funções:

def calcular_media(nota1: float, nota2: float) -> float:
    media = (nota1 + nota2) / 2
    return media

def situacao(nota: float) -> str:
    if nota >= 7:
        return "aprovado"
    if nota >= 5:
        return "recuperação"
    return "reprovado"

def mostrar_resultado(media: float) -> None:
    print(f"Média: {media:.1f} ({situacao(media)})")

def main() -> None:
    media = calcular_media(8.0, 6.0)
    mostrar_resultado(media)

main()

Saída do Código:

Média: 7.0 (aprovado)

As duas versões imprimem a mesma coisa. A segunda é mais longa, mas cada função tem uma tarefa só e um nome que diz qual é, e a main() se lê como um resumo do programa: calcular a média e mostrar o resultado. Para mudar a regra de aprovação, mexe-se só em situacao; para mudar o formato da saída, só em mostrar_resultado. E calcular_media e situacao são as mesmas das seções de escopo e do return: uma função escrita uma vez é reaproveitada em outro programa.

Chamar a função principal de main é uma convenção, não uma regra do Python. No Capítulo 8 vem o passo seguinte: guardar funções em arquivos separados, os módulos, e usá-las com import.

Exercícios#

  1. Menor e Maior entre Três Números

Desenvolva um programa que determine o menor e o maior número entre três valores fornecidos pelo usuário. O programa deve utilizar funções para realizar as operações necessárias. Siga as instruções:

  • Crie uma função chamada ler_numeros() que solicite ao usuário que insira três números e retorne esses valores.

  • Implemente uma função chamada encontrar_menor_maior(a, b, c) que receba três números como parâmetros e retorne uma tupla contendo o menor e o maior valor, nesta ordem.

  • No programa principal, utilize as funções criadas para:

    • Obter os três números do usuário.

    • Calcular o menor e o maior valor.

    • Exibir os resultados.

Exemplo de uso no programa principal:

num1, num2, num3 = ler_numeros()
menor, maior = encontrar_menor_maior(num1, num2, num3)
print(f"O menor número é: {menor}")
print(f"O maior número é: {maior}")
este bloco é a chamada esperada das funções que você vai escrever neste exercício

Certifique-se de que o programa trate adequadamente diferentes tipos de entrada, incluindo números inteiros e decimais.

# Teste 1
Entrada: 10, 5, 8
Saída: 
O menor número é: 5
O maior número é: 10

# Teste 2
Entrada: 3.14, 1.71, 1.41
Saída:
O menor número é: 1.41
O maior número é: 3.14

# Teste 3
Entrada: -5, 0, -10
Saída:
O menor número é: -10
O maior número é: 0

# Teste 4
Entrada: 1, 3.14, 2
Saída:
O menor número é: 1
O maior número é: 3.14
  1. Cálculo do IMC Utilizando Funções

Desenvolva um programa em Python para calcular o Índice de Massa Corporal (IMC) de uma pessoa, utilizando funções para organizar e modularizar o código.

O programa deve incluir as seguintes funções:

  • Uma função para ler os dados do usuário (peso em kg e altura em metros).

  • Uma função para calcular o IMC usando a fórmula: IMC = peso / (altura * altura).

  • Uma função para classificar o IMC de acordo com as faixas padrão.

A classificação do IMC deve seguir a tabela abaixo:

IMC (kg/m²)

Classificação

< 18.5

Abaixo do peso

18.5 - 24.9

Peso normal

25.0 - 29.9

Sobrepeso

30.0 - 34.9

Obesidade grau 1

35.0 - 39.9

Obesidade grau 2

≥ 40.0

Obesidade grau 3

No programa principal, chame as funções na ordem adequada para:

  • Obter os dados do usuário.

  • Calcular o IMC.

  • Determinar a classificação.

Por exemplo:

peso, altura = ler_dados()
imc = calcular_imc(peso, altura)
classificacao = classificar_imc(imc)
print(f"Seu IMC é {imc:.2f} - Classificação: {classificacao}")
este bloco é a chamada esperada das funções que você vai escrever neste exercício

Certifique-se de tratar possíveis erros de entrada do usuário, como valores negativos ou zero para peso e altura. O programa deve ser robusto e capaz de lidar com diferentes cenários de entrada.

# Teste 1
Entrada: 90.5, 1.85
Saída: Seu IMC é 26.44 - Classificação: Sobrepeso

# Teste 2
Entrada: 65.0, 1.75
Saída: Seu IMC é 21.22 - Classificação: Peso normal

# Teste 3
Entrada: 45.0, 1.60
Saída: Seu IMC é 17.58 - Classificação: Abaixo do peso

# Teste 4
Entrada: 120.0, 1.70
Saída: Seu IMC é 41.52 - Classificação: Obesidade grau 3
  1. Calculadora de Área de Formas Geométricas

Crie um programa que funcione como uma calculadora de área para diferentes formas geométricas, incluindo triângulo, círculo e retângulo. O programa deve fornecer um menu fácil de usar para que o usuário selecione a forma desejada e, em seguida, insira as dimensões necessárias para calcular a área.

Crie uma função principal chamada calcular_area() que representa a abstração de um menu ao usuário com as seguintes opções:

  • Opção 1: Calcular a área de um triângulo (base e altura)

  • Opção 2: Calcular a área de um círculo (raio)

  • Opção 3: Calcular a área de um retângulo (comprimento e largura)

  • Opção 4: Sair do programa

Crie individualmente uma função para o cálculo das áreas de cada forma geométrica:

  • area_triangulo(base, altura)

  • area_circulo(raio)

  • area_retangulo(comprimento, largura)

Exemplos de teste:

# Teste 1
Entrada: 
1
5
10
Saída: A área do triângulo é: 25.00

# Teste 2
Entrada:
2
3
Saída: A área do círculo é: 28.27

# Teste 3
Entrada:
3
4
6
Saída: A área do retângulo é: 24.00

# Teste 4
Entrada: 4
Saída: Programa encerrado.

Observações:

  • Use a biblioteca math para obter o π (pi) nos cálculos do círculo.

  • Arredonde todos os resultados para duas casas decimais.

4. Sistema de Bonificação para Atletas Olímpicos

O Comitê Olímpico Nacional decidiu criar um sistema de bonificação para atletas que ganharam medalhas nas Olimpíadas. O sistema funciona da seguinte forma:

Cada atleta recebe uma bonificação base de acordo com a medalha:

  • Ouro: R$ 50.000

  • Prata: R$ 30.000

  • Bronze: R$ 10.000

Além disso, há bônus adicionais baseados em critérios específicos:

  • Quebra de recorde mundial: +R$ 100.000

  • Primeira medalha do país na modalidade: +R$ 50.000

Crie uma função chamada calcular_bonificacao que receba os seguintes parâmetros:

  • medalhas: uma lista de strings representando as medalhas ganhas (“ouro”, “prata” ou “bronze”)

  • recorde_mundial: Uma String, Sim ou Nao, indicando se o atleta quebrou um recorde mundial

  • primeira_medalha_pais: Uma String, Sim ou Nao, indicando se é a primeira medalha do país na modalidade

A função deve retornar o valor total da bonificação do atleta.

# Teste 1
Entrada:Ouro,Sim,Nao
Saída: 150000

# Teste 2
Entrada:Prata,Bronze,Nao,Sim
Saída: 90000

# Teste 3
Entrada:Bronze,Bronze,Bronze,Nao,Nao
Saída: 30000

# Teste 4
Entrada:Ouro,Prata,Bronze,Nao,Sim
Saída: 140000

5. Calculando as Raízes de Equações Quadráticas

Neste exercício, você terá a oportunidade de aplicar conceitos matemáticos para resolver o problema de Bhaskara, escrevendo um código para calcular as raízes de uma equação quadrática.

Dada uma equação quadrática na forma \(Ax^2 + Bx + C = 0\), escreva um programa que leia três valores de ponto flutuante e calcule as raízes da equação usando a Fórmula de Bhaskara. Se não for possível calcular as raízes, mostre a mensagem “Impossível calcular”, caso haja uma divisão por zero ou raiz de número negativo.

\( R1 = \frac{-B + \sqrt{B^2 - 4AC}}{2A} \)

\( R2 = \frac{-B - \sqrt{B^2 - 4AC}}{2A} \)

Observações:

  • Se \(A\) é igual a zero, não é possível calcular as raízes.

  • Se o discriminante \(\Delta = B^2 - 4AC \) é negativo, não é possível calcular as raízes.

  • As raízes da equação devem ter 2 dígitos após o ponto decimal.

  • Escreva uma função para leitura dos dados, outra para verificar se é possível calcular as raízes, e uma terceira para calcular e retornar as raízes.

# Teste 1
Entrada: 10.0, 19.1, 5.2
Saída:
R1 = -0.33
R2 = -1.58

# Teste 2
Entrada: 0.0, 20.0, 5.0
Saída: Impossível calcular

# Teste 3
Entrada: 1.0, -3.0, 2.0
Saída:
R1 = 2.00
R2 = 1.00

# Teste 4
Entrada: 1.0, 2.0, 5.0
Saída: Impossível calcular

6. Contando: Pares, Ímpares, Positivos e Negativos

Escreva uma função chamada contar_pares_impares que receba uma lista de números como argumento e retorne duas variáveis: a contagem de números pares e a contagem de números ímpares na lista.

Escreva outra função chamada contar_positivos_negativos que receba a mesma lista de números como argumento e retorne duas variáveis: a contagem de números positivos e a contagem de números negativos na lista.

O programa principal deve ler uma lista de números inteiros na entrada, passar essa lista para as funções acima. Em seguida, o programa deve imprimir as contagens de números pares, ímpares, positivos e negativos, cada uma em uma linha separada.

# Teste 1
Entrada: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25
Saída:
Quantidade de números pares: 24
Quantidade de números ímpares: 26
Quantidade de números positivos: 25
Quantidade de números negativos: 25

# Teste 2
Entrada: 30, -31, 32, -33, 34, -35, 36, -37, 38, -39, 40, -41, 42, -43, 44, -45, 46, -47, 48, -49, 50, -51, 52, -53, 54, -55, 56, -57, 58, -59, 60, -61, 62, -63, 64, -65, 66, -67, 68, -69, 70, -71, 72, -73, 74, -75
Saída:
Quantidade de números pares: 23
Quantidade de números ímpares: 23
Quantidade de números positivos: 23
Quantidade de números negativos: 23

7. Soma Recursiva de uma Lista

Escreva uma função recursiva chamada soma_recursiva que receba uma lista de números inteiros como entrada e retorne a soma de todos os elementos da lista.

Por exemplo, se a lista de entrada for [1, 2, 3, 4, 5], a função deve retornar 15 (que é a soma de todos os números na lista).

Observações:

  • A função soma_recursiva deve ter uma condição base, que é quando a lista está vazia.

  • A função deve chamar a si mesma com uma versão menor da lista, até chegar na condição base.

  • A função deve realizar a soma dos elementos da lista de forma recursiva.

Teste sua função com as seguintes entradas:

# Teste 1
entrada = 1, 2, 3, 4, 5, 50, 60, 70, 80, 90, 0, 1
saida = 366

# Teste 2
entrada = 10, 20, 30, 40, 50, 1, 2, 3, 4, 5, 6, 7, -15, -20
saida = 143

8. Cálculo da Sequência de Fibonacci

Imagine que você tem uma lista de números. O objetivo é calcular o n-ésimo número da sequência de Fibonacci para cada número dessa lista e imprimir a soma total desses números.

A sequência de Fibonacci é como uma escada em espiral, onde cada número é a soma dos dois anteriores. Ela começa com 0 e 1, e continua infinitamente: 0, 1, 1, 2, 3, 5, 8, 13, 21…

Para isso, você precisará criar duas funções recursivas:

  1. A função Fibonacci: Essa função será o coração da operação. Ela receberá um número como entrada e, usando a recursão, calculará o número correspondente na sequência de Fibonacci.

  2. A função Soma Recursiva: Essa função receberá a lista de resultados da função Fibonacci e, novamente usando a recursão, somará todos os elementos dessa lista.

No final, você imprimirá na tela a soma total de todos os números da sequência de Fibonacci calculados para cada número da lista de entrada.

Lembre-se: a recursão é como um espelho que reflete a si mesmo. A função chama a si mesma para resolver problemas menores até chegar a um ponto de parada, chamado de “condição base”. É como descer uma escada: você dá um passo de cada vez até chegar ao chão.

# Teste 1
Entrada: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Saída: 1596

# Teste 2
Entrada: 10, 11, 9, 15, 17, 23, 13, 20, 12, 11, 22
Saída: 55984

9. Cálculo da Potência de Forma Recursiva

Escreva uma função recursiva chamada calcular_potencia que receba dois números como entrada: a base (base) e o expoente (expoente). A função deve calcular e retornar o resultado da base elevada ao expoente, sem utilizar a função math.pow() ou qualquer outra função matemática avançada, apenas operações matemáticas elementares.

Por exemplo, se a base for 2 e o expoente for 3, a função deve retornar 8 (que é o resultado de 2 elevado a 3).

Observações:

  • A função calcular_potencia deve ter uma condição base, que é quando o expoente é igual a 0.

  • A função deve chamar a si mesma com uma versão menor do expoente, até chegar na condição base.

  • A função deve realizar o cálculo da potência de forma recursiva, usando apenas operações matemáticas básicas (multiplicação, divisão, adição e subtração).

Teste sua função com as seguintes entradas:

# Teste 1
Entrada: 2, 3
Saída: 2^3 = 8

# Teste 2
Entrada: 4, 5
Saída: 4^5 = 1024

# Teste 3
Entrada: 6, 4
Saída: 6^4 = 1296

# Teste 4
Entrada: 34, 0
Saída: 34^0 = 1

Lembre-se de implementar a função calcular_potencia de forma recursiva, usando apenas operações matemáticas básicas.

10. Torre de Hanói: Um Desafio Recursivo da Computação e Matemática

A Torre de Hanói é um problema clássico e intrigante no campo da computação e matemática. Envolve a movimentação estratégica de discos entre três pinos, seguindo um conjunto específico de regras. O desafio parece simples à primeira vista, mas sua complexidade aumenta exponencialmente à medida que o número de discos cresce.

O Problema: Imagine três pinos, rotulados como A, B e C. No início do jogo, todos os discos, que variam em tamanho, estão empilhados em ordem decrescente de tamanho no pino A, com o disco maior embaixo e o menor no topo. O objetivo é transferir todos os discos do pino A para o pino C.

As Regras:

  • Você só pode mover um disco por vez.

  • Cada movimento envolve tirar o disco de cima de uma pilha e colocá-lo em cima de outra pilha.

  • Nunca coloque um disco maior em cima de um menor. Os discos devem ser empilhados em ordem de tamanho.

  • O pino B é auxiliar. Você pode usá-lo como um pino intermediário para ajudar na movimentação dos discos.

Visualizando o Jogo:

A animação a seguir resolve o jogo com três discos, nos sete movimentos do Teste 1. À direita, a recursão aparece como uma pilha de tarefas: uma tarefa nova entra em azul, a que move um disco fica roxa e, quando a de baixo termina, a de cima retoma em laranja.

Animação em quarenta e três quadros da Torre de Hanói com três discos. À esquerda, no alto, os pinos A, B e C, com os três discos começando no A, e embaixo a saída. À direita, a pilha de tarefas da recursão, escrita em palavras. A tarefa mover 3 de A para C abre mover 2 de A para B, que abre mover 1 de A para C, e assim por diante. Cada disco que se move sobe, atravessa e desce, e a saída ganha a linha correspondente, até os sete movimentos do Teste 1, Mover disco de A para C, de A para B, de C para B, de A para C, de B para A, de B para C e de A para C, com os três discos no pino C. Uma tarefa nova entra em azul, a que move um disco fica roxa e, quando a de baixo termina, a de cima retoma em laranja Animação em quarenta e três quadros da Torre de Hanói com três discos. À esquerda, no alto, os pinos A, B e C, com os três discos começando no A, e embaixo a saída. À direita, a pilha de tarefas da recursão, escrita em palavras. A tarefa mover 3 de A para C abre mover 2 de A para B, que abre mover 1 de A para C, e assim por diante. Cada disco que se move sobe, atravessa e desce, e a saída ganha a linha correspondente, até os sete movimentos do Teste 1, Mover disco de A para C, de A para B, de C para B, de A para C, de B para A, de B para C e de A para C, com os três discos no pino C. Uma tarefa nova entra em azul, a que move um disco fica roxa e, quando a de baixo termina, a de cima retoma em laranja

Sua Tarefa: Implemente uma função recursiva chamada torre_de_hanoi que resolva esse problema. A função deve receber como entrada o número de discos (n) e os nomes dos pinos de origem, auxiliar e destino. Ela deve executar e imprimir os movimentos necessários para transferir todos os discos do pino de origem para o pino de destino.

Protótipo da Função:

def torre_de_hanoi(n, origem, auxiliar, destino):
    # Implementação aqui

Dicas e Observações:

  • A função deve ter uma condição base para quando houver apenas um disco.

  • Use a recursividade para dividir o problema em subproblemas menores.

  • Imprima cada movimento no formato: “Mover disco de [origem] para [destino]”.

  • A solução recursiva deve:

    • Mover n-1 discos para o pino auxiliar

    • Mover o disco maior para o pino de destino

    • Mover os n-1 discos do pino auxiliar para o pino de destino

Testes:

# Teste 1
Entrada: 3, A, B, C
Saída:
Mover disco de A para C
Mover disco de A para B
Mover disco de C para B
Mover disco de A para C
Mover disco de B para A
Mover disco de B para C
Mover disco de A para C

# Teste 2
Entrada: 4, A, B, C
Saída:
Mover disco de A para B
Mover disco de A para C
Mover disco de B para C
Mover disco de A para B
Mover disco de C para A
Mover disco de C para B
Mover disco de A para B
Mover disco de A para C
Mover disco de B para C
Mover disco de B para A
Mover disco de C para A
Mover disco de B para C
Mover disco de A para B
Mover disco de A para C
Mover disco de B para C

Exercícios (Extra)#

1. Analisador de Desempenho: Planeje seus Estudos!

A partir das notas de 7 disciplinas, calcule métricas de desempenho e gere um ranking de prioridade de estudo. Cada disciplina tem 3 unidades; o usuário fornece as 6 notas das duas primeiras unidades (3 avaliações cada), e o sistema calcula a nota necessária na 3ª unidade para atingir média 5.0.

Disciplinas (nesta ordem):

  1. Linguagem de Programação

  2. Álgebra Linear

  3. Modelagem do Mundo Físico I

  4. Práticas de Leitura e Escrita II

  5. Química Geral

  6. Cálculo Diferencial e Integral I

  7. Meio Ambiente e Desenvolvimento Urbano

Entrada: 42 notas (7 disciplinas × 6 notas).

Métricas a calcular (por disciplina):

  • Média da unidade: \(\dfrac{\text{soma das 3 notas}}{3}\)

  • Nota necessária na 3ª unidade: \(3 \times 5.0 - (\text{Média 1ª} + \text{Média 2ª})\)

  • Consistência: \(1 - \dfrac{|\text{Média 2ª} - \text{Média 1ª}|}{\text{Média 1ª}}\) (próximo de 1 = estável; próximo de 0 = instável)

  • Instabilidade: \(\dfrac{\text{Desvio Padrão 2ª}}{\text{Desvio Padrão 1ª}}\) (=1 igual; >1 mais dispersa na 2ª; <1 mais dispersa na 1ª)

  • Prioridade: \(2.0 \times (1 - \text{Consistência}) + 1.5 \times \text{Instabilidade} + 1.0 \times \text{Nota Necessária}\)

Tarefa: implementar analisar_desempenho, que recebe as 42 notas e funções auxiliares, calcula as métricas e imprime o ranking ordenado por prioridade decrescente. Use sorted para ordenar.

Protótipo:

def analisar_desempenho(
    notas: list[float],
    calcular_media,
    calcular_desvio_padrao,
    calcular_nota_necessaria,
    avaliar_consistencia,
    gerar_ranking_prioridade
) -> None:
    """Analisa desempenho acadêmico e imprime o ranking de prioridade."""
    pass

Teste:

# Teste 1
Entrada:
6.0, 7.0, 8.0, 5.0, 6.5, 6.0,
4.0, 5.5, 6.0, 4.5, 5.0, 4.0,
7.0, 8.0, 7.5, 6.0, 7.0, 6.5,
6.0, 6.5, 7.0, 6.0, 6.0, 5.5,
5.0, 5.5, 5.0, 4.5, 4.0, 4.0,
6.0, 7.0, 6.5, 5.5, 6.0, 5.0,
7.0, 6.5, 6.0, 6.5, 6.0, 5.5

Saída:
Ranking de prioridade para estudo:
1. Química Geral:
   - Consistência: 0.81
   - Instabilidade: 1.00
   - Nota necessária na 3ª unidade: 5.67
   - Prioridade: 7.55
2. Álgebra Linear:
   - Consistência: 0.87
   - Instabilidade: 0.48
   - Nota necessária na 3ª unidade: 5.33
   - Prioridade: 6.31
3. Cálculo Diferencial e Integral I:
   - Consistência: 0.85
   - Instabilidade: 1.00
   - Nota necessária na 3ª unidade: 3.00
   - Prioridade: 4.81
4. Meio Ambiente e Desenvolvimento Urbano:
   - Consistência: 0.92
   - Instabilidade: 1.00
   - Nota necessária na 3ª unidade: 2.50
   - Prioridade: 4.15
5. Práticas de Leitura e Escrita II:
   - Consistência: 0.90
   - Instabilidade: 0.58
   - Nota necessária na 3ª unidade: 2.67
   - Prioridade: 3.74
6. Linguagem de Programação:
   - Consistência: 0.83
   - Instabilidade: 0.76
   - Nota necessária na 3ª unidade: 2.17
   - Prioridade: 3.65
7. Modelagem do Mundo Físico I:
   - Consistência: 0.87
   - Instabilidade: 1.00
   - Nota necessária na 3ª unidade: 1.00
   - Prioridade: 2.77