Pular para conteúdo

12. Funções: def, parâmetro e o que ela devolve

Nesta aula

Ideia central: o semestre inteiro você chamou funções prontas — print, input, len, round, range — sem precisar saber o nome disso. Esta aula ensina a escrever as suas: um pedaço de programa com nome, que recebe valores e, na maioria das vezes, devolve uma resposta para quem chamou usar.

Ao fim você vai conseguir: definir uma função com def; distinguir parâmetro (na definição) de argumento (na chamada); usar return para devolver um valor; explicar por que uma função que só imprime é mais limitada que uma que devolve; e reconhecer o defeito central desta unidade — uma função que "funciona" na tela e devolve None.

Você já chama funções há treze capítulos

print("oi"), input(), len(lista), round(3.14159, 2) — todos eles têm a mesma forma: um nome, seguido de parênteses, com valores entre eles. Isso é chamar uma função. Você só nunca precisou definir uma, porque o Python já trouxe essas prontas.

A partir de hoje você escreve as suas.

Definir não é executar

def saudacao(nome):
    print(f"Bem-vindo, {nome}!")


saudacao("Ana")
saudacao("Bruno")
Bem-vindo, Ana!
Bem-vindo, Bruno!

O bloco def saudacao(nome): não imprime nada sozinho — ele só ensina ao Python o que fazer quando alguém chamar saudacao(...). Nada acontece até a primeira linha fora da função, saudacao("Ana"), rodar de verdade. É a mesma diferença de uma receita e o prato pronto: escrever a receita não alimenta ninguém.

💡 Pense assim: a receita e o prato

def escreve a receita: o passo a passo, guardado com um nome. Cada chamada — saudacao("Ana"), saudacao("Bruno") — é fazer o prato uma vez, com os ingredientes daquela vez. A mesma receita, prato depois de prato, com ingredientes diferentes.

Parâmetro × argumento

def situacao_franquia(consumo_gb, franquia_gb):
    print(f"Consumo: {consumo_gb} GB | Franquia: {franquia_gb} GB")
    if consumo_gb > franquia_gb:
        print("Passou da franquia")
    else:
        print("Dentro da franquia")


situacao_franquia(12, 10)
situacao_franquia(7, 10)
Consumo: 12 GB | Franquia: 10 GB
Passou da franquia
Consumo: 7 GB | Franquia: 10 GB
Dentro da franquia

Dois nomes para duas coisas diferentes, e a disciplina seguinte já vai supor que você os distingue:

Termo Onde aparece No exemplo
parâmetro na linha do def, é um nome que ainda não tem valor consumo_gb, franquia_gb
argumento na chamada, é o valor de verdade que você está passando 12 e 10, depois 7 e 10

A função foi definida uma vez. Cada chamada manda argumentos diferentes, e o corpo dela roda de novo, do zero, com esses valores nos parâmetros. Repare que a segunda chamada não "lembra" nada da primeira.

return: devolver um valor para quem chamou

Tudo que você viu até aqui só imprime — mostra alguma coisa na tela, para uma pessoa ler. Mas um programa quase sempre precisa usar o resultado de uma conta em outra conta, e é aí que print não basta.

def calcula_excedente(consumo_gb, franquia_gb):
    excedente = consumo_gb - franquia_gb
    print(excedente)


calcula_excedente(12, 10)
2

Essa função calcula o excedente e mostra 2 na tela. Funciona — até você precisar do excedente para calcular quanto cobrar por ele. O 2 que apareceu não está guardado em lugar nenhum; ele foi escrito na tela e sumiu. Para usá-lo de novo você teria que copiar a conta inteira.

Compare com a mesma função usando return:

def calcula_excedente(consumo_gb, franquia_gb):
    excedente = consumo_gb - franquia_gb
    return excedente


excedente = calcula_excedente(12, 10)
custo = excedente * 5
print(f"Excedente: {excedente} GB")
print(f"Cobranca extra: R$ {custo}")
Excedente: 2 GB
Cobranca extra: R$ 10

return excedente entrega o valor calculado para quem chamou a função — e excedente = calcula_excedente(12, 10) guarda essa entrega numa variável, do mesmo jeito que input() guarda o que a pessoa digitou. Com o valor em mãos, o programa multiplica por 5 para achar o custo, sem repetir a conta do excedente em lugar nenhum. É por isso que devolver é quase sempre melhor que imprimir: o resultado de uma função com return pode ser guardado, somado, comparado, formatado — usado de novo em qualquer lugar do programa. O resultado de uma função que só imprime não pode ser usado em lugar nenhum: ele já foi para a tela, e a tela não devolve nada ao programa.

💡 Pense assim: o caixa eletrônico e o extrato pregado na parede

Uma função com return é o caixa eletrônico: ele entrega o dinheiro na sua mão, e você decide o que fazer com ele — guardar, gastar, contar de novo. Uma função que só imprime é o extrato pregado na parede do banco: você lê o número, mas não sai dali com nada que o programa consiga seguir usando.

A armadilha: a função que imprime o número certo e devolve None

Este é o defeito central da unidade, e ele é traiçoeiro porque parece funcionar.

def dobro(numero):
    print(numero * 2)  # imprime, mas nao devolve nada


resultado = dobro(5)
print(resultado)
10
None

dobro(5) imprime 10 — o número certo, na tela, na hora certa. Quem está olhando jura que a função funcionou. Mas resultado = dobro(5) não guarda o 10 em lugar nenhum, porque dobro nunca usou return. Sem return, toda função Python devolve None por padrão — um valor que significa "nada aqui". resultado vale None, e print(resultado) mostra exatamente isso.

print mostra; return entrega. Uma função sem return sempre devolve None

Não importa quantos print existam dentro dela: se não houver um return, o valor que a função devolve é None. O programa não trava, não avisa nada — o 10 na tela é real, e ainda assim resultado está vazio. Só quebra na hora em que alguém tenta usar resultado numa conta, e aí a causa já está longe, lá na função que "funcionava".

Função que imprime Função que devolve
O que faz mostra um valor na tela entrega um valor para quem chamou
Termina em print(...) return ...
Dá para guardar o resultado numa variável? não — vem None sim
Dá para usar o resultado em outra conta? não sim
Dá para testar com assert? não, direto sim — é o que a Lista 12 faz

Uma função pode ter vários parâmetros — e geralmente um só retorno

situacao_franquia(consumo_gb, franquia_gb), lá em cima, já tinha dois parâmetros: a ordem em que você os escreve no def é a mesma ordem em que os argumentos entram na chamada. Uma função pode receber quantos parâmetros precisar, mas quase sempre devolve um valor por chamada — se ela precisa calcular duas coisas diferentes, o mais comum é escrever duas funções, uma para cada uma.

Erros comuns deste capítulo

Mensagem ou sintoma Causa provável
TypeError: nome_da_funcao() missing 1 required positional argument: 'x' chamou a função com menos argumentos do que ela tem parâmetros
TypeError: nome_da_funcao() takes 2 positional arguments but 3 were given chamou com argumentos a mais
SyntaxError: expected ':' faltaram os dois-pontos no fim da linha do def
IndentationError: expected an indented block o corpo da função não foi recuado
a tela mostrou o número certo, mas a variável guardada é None a função usa print onde devia usar return
<function nome_da_funcao at 0x...> no lugar do resultado esperado esqueceu os parênteses na chamada — escreveu nome_da_funcao, não nome_da_funcao()

Resumo

  • def nome(parametros): define a função; nada dela roda até alguém chamar nome(argumentos).
  • Parâmetro é o nome na definição; argumento é o valor na chamada.
  • return valor devolve o valor para quem chamou, e permite guardá-lo numa variável e reutilizá-lo. print só mostra na tela, para uma pessoa ler.
  • O erro que não dá erro desta unidade: uma função sem return "funciona" na tela — mostra o número certo — e devolve None para o programa. Só quebra quando alguém tenta usar o resultado.
  • Uma função pode ter vários parâmetros, mas normalmente devolve um valor só.

Para praticar

Lista 12 · Funções.

Todo exercício desta lista é, pela primeira vez, uma função com testes prontos no fim do arquivo — o mesmo formato de Python para Telecomunicações. Antes de entregar qualquer um deles, pergunte-se: minha função devolve o valor, ou só mostra na tela?