Funções e módulos

Parâmetros, retorno, âmbito, argumentos e validação de funções pequenas.

Perguntar sobre esta página

ChatGPTClaudePerplexityGeminiCopiar e abrir

Brain rot

A seguirPara ti

@resumos.feup

som original ·

Definições

Leitura

Mudar de trecho
Legendas
Mostrar no vídeo
Vídeos de fundo

O texto, o áudio e os teus vídeos ficam neste dispositivo.

Créditos dos vídeos e da voz

Partilhar página

Uma função recebe dados, calcula um resultado e pode devolvê-lo ao código que a chamou. Usa-a quando queres dar nome a um cálculo e aplicá-lo a entradas diferentes.

Devolver um resultado

Vamos calcular o preço de uma compra com desconto. O desconto é uma fração entre 0 e 1; 0.10 significa 10 por cento.

Calcular e devolver
def total_compra(quantidade, preco, desconto=0):
    if quantidade < 0 or preco < 0:
        raise ValueError("Quantidade e preço devem ser não negativos")
    if not 0 <= desconto <= 1:
        raise ValueError("Desconto fora do intervalo [0, 1]")
    return quantidade * preco * (1 - desconto)

print(total_compra(3, 2.5))
print(total_compra(3, 2.5, desconto=0.10))
Dados de entrada

A saída é 7.5 e 6.75. O return termina a chamada e entrega o valor. Se uma função chega ao fim sem return, devolve None.

print mostra algo, mas não devolve esse valor. Uma função que apenas escreve 7.5 não permite somar o resultado a outra compra. Mantém o cálculo separado da apresentação quando precisas de reutilizar o valor.

Parâmetros e argumentos

Os nomes na definição, quantidade, preco e desconto, são parâmetros. Os valores fornecidos na chamada são argumentos.

total_compra(3, 2.5) usa posições. total_compra(preco=2.5, quantidade=3) usa nomes. O valor por defeito só se aplica quando omites o argumento. Não pode haver dois argumentos diferentes para o mesmo parâmetro.

Argumentos posicionais devem aparecer antes dos argumentos por nome. *args pode recolher argumentos posicionais adicionais num tuplo e **kwargs argumentos por nome num dicionário. Usa-os quando a quantidade variável faz parte do problema, não para evitar definir os dados necessários.

Número variável de argumentos e lambda

*valores recolhe os argumentos posicionais num tuplo. Uma chamada com uma lista só passa um argumento; *lista na chamada expande os seus elementos.

def somar(*valores):
    return sum(valores)

print(somar(2, 4, 6))    # 12
print(somar(*[2, 4, 6])) # 12

**opcoes recolhe argumentos por nome num dicionário. A expansão **dicionario fornece argumentos pelos nomes das chaves. Usa nomes explícitos quando a função precisa sempre dos mesmos dados.

Uma expressão lambda x: x * x cria uma função que devolve uma única expressão. Pode servir como chave de ordenação ou argumento de map. map produz um iterador; list(map(...)) materializa os resultados.

quadrados = list(map(lambda x: x * x, [2, 3, 4]))
print(quadrados)  # [4, 9, 16]

Para várias instruções, validação ou um cálculo que precisa de um nome claro, usa def.

Âmbito e mutabilidade

Um nome atribuído dentro da função é normalmente local à chamada. Não altera automaticamente um nome com o mesmo nome fora dela. Mas uma função que recebe uma lista pode alterar essa lista:

def acrescentar_zero(valores):
    valores.append(0)

O chamador observa a alteração porque partilha o objeto. Se queres devolver outra lista, escreve return valores + [0]. Documenta qual das duas operações prometes.

Valores por defeito são criados quando a definição da função é executada. Por isso, evita uma lista como argumento por defeito:

def juntar(valor, valores=None):
    if valores is None:
        valores = []
    valores.append(valor)
    return valores

Agora cada chamada que omite valores cria uma lista nova. def juntar(valor, valores=[]): reutilizaria a mesma lista entre chamadas.

Ler e alterar um nome global

Uma função pode ler um nome definido no módulo. Uma atribuição dentro da função cria normalmente um nome local, mesmo quando existe um global com o mesmo nome. global declara que a atribuição deve alterar o nome do módulo.

limite = 10

def novo_limite():
    limite = 20
    return limite

print(novo_limite(), limite)  # 20 10

Se acrescentares global limite antes da atribuição na função, o segundo valor passa a 20. Ler um nome local antes da sua atribuição pode causar UnboundLocalError. Passar o limite como parâmetro e devolver o novo valor torna a dependência visível e evita depender da ordem de chamadas.

Testar um contrato pequeno

Um contrato diz que entradas são válidas e que resultado deve ser devolvido. Para a média, vamos exigir uma sequência não vazia de números:

Conferir o contrato
def media(valores):
    if not valores:
        raise ValueError("A média precisa de uma observação")
    return sum(valores) / len(valores)

assert media([4]) == 4
assert media([2, 4, 6]) == 4
assert media([-2, 2]) == 0
try:
    media([])
except ValueError:
    print("Caso vazio rejeitado")
else:
    raise AssertionError("O caso vazio devia causar ValueError")
print("Casos verificados")
Dados de entrada

Os testes verificam uma observação, vários valores, sinais diferentes e o caso vazio. Não tratam a média de uma lista vazia como zero, porque zero seria um resultado numérico inventado.

Importar um módulo

Um módulo reúne nomes relacionados. import math permite usar math.sqrt(9). from math import sqrt permite usar sqrt(9). import numpy as np dá um nome curto ao módulo, não copia os seus dados.

Não chames ao teu ficheiro pandas.py ou math.py, porque pode esconder o módulo que queres importar. Num projeto com vários ficheiros, executa o programa a partir da pasta indicada e mantém os imports reproduzíveis.

Rever chamadas e retorno

Exercícios

Distinguir saída de retorno

Uma função executa print(5), sem return. Que valor recebe resultado = funcao()?

A tua resposta

Primeira pista
Uma função pode produzir saída e ter outro retorno.
Mais uma pista
Sem return explícito, Python devolve None.
Ver solução

resultado recebe None. A consola mostra 5 como efeito da execução.

  • None. A função não devolveu explicitamente um valor.
  • 5. 5 é escrito na saída, não devolvido.
  • "5". A representação impressa não é o valor de retorno.

Erros frequentes

Confundir o que aparece no ecrã com o que pode ser usado no cálculo seguinte.

Voltar à explicação
Justificar entradas inválidas

Define uma média que rejeita uma sequência vazia. Explica por que devolver zero no caso vazio altera o significado da função.

Primeira pista
A média divide a soma pelo número de observações.
Mais uma pista
Sem observações, o denominador seria zero.
Ver solução

Verifica a entrada e lança ValueError se estiver vazia. Para entradas válidas, devolve sum(valores) / len(valores). Zero seria a média real de alguns conjuntos e confundiria essas situações com dados inexistentes.

Confere a tua resposta:

  • Defini a entrada válida como uma sequência não vazia de números.
  • Verifiquei o caso vazio antes da divisão.
  • Expliquei que zero é um resultado numérico, não uma ausência de observações.

Erros frequentes

Ignorar o contrato, dividir por zero ou acrescentar um elemento fictício.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.