Funções
Contratos, retorno, argumentos, âmbito léxico, efeitos e decomposição de problemas.
Para decidir se 6 é um número perfeito, somamos os seus divisores positivos menores que 6: . Podemos separar essa soma da comparação com o número original. Uma função dá um nome a cada cálculo e permite chamá-lo com argumentos diferentes, sem repetir o código.
Definir e chamar
def define uma função. Os nomes na definição são parâmetros; os valores de uma chamada são argumentos. Os dois pontos e a indentação delimitam o corpo, como num if. Segue quadrado(5): a chamada associa x a 5, calcula 5 * 5 e return entrega 25 ao chamador.
def quadrado(x):
return x * x
print(quadrado(5))
print(quadrado(2) + quadrado(3))
A saída é 25 e 13. Definir a função não executa o seu corpo. Cada chamada tem os seus nomes locais. O resultado de uma chamada pode participar noutra expressão.
Return e print
return termina a chamada e entrega um valor ao chamador. print escreve na saída e devolve None. Uma função que chega ao fim sem return, ou executa return sem expressão, também devolve None.
def saudar(nome):
print("Olá,", nome)
resposta = saudar("Ana")
print(resposta)
A saída é Olá, Ana e depois None. A saudação é um efeito da chamada, não o seu resultado. Se pretendes reutilizar o texto, escreve return "Olá, " + nome e imprime apenas fora da função. Num exercício que pede uma função, confirma se deve devolver ou imprimir.
Contrato e decomposição
O contrato indica o que a função recebe, as condições exigidas e o resultado. soma_divisores recebe um inteiro positivo e devolve a soma dos seus divisores próprios. e_perfeito recebe qualquer inteiro e devolve False para os não positivos. Uma docstring descreve o contrato entre aspas triplas no início do corpo; não valida a entrada.
def soma_divisores(n):
"""Soma os divisores próprios do inteiro positivo n."""
soma = 0
for d in range(1, n):
if n % d == 0:
soma += d
return soma
def e_perfeito(n):
return n > 0 and soma_divisores(n) == n
print(e_perfeito(6), e_perfeito(12), e_perfeito(28))
print(e_perfeito(1), e_perfeito(0))Executa o código para ver a saída e os gráficos.
Para 6, range(1, 6) visita 1, 2, 3, 4 e 5; o teste 6 % d == 0 aceita os três primeiros e rejeita 4 e 5, pelo que o acumulador termina em 6 e a função exterior confirma a igualdade com a entrada.
Os divisores próprios de 12 somam , por isso 12 não é perfeito, enquanto para 28 a soma confirma que é. O 1 tem soma vazia de divisores próprios, igual a zero, por isso também não é perfeito.
A função exterior decide e a interior resolve a soma; n > 0 and ... evita chamar a função interior fora da sua condição de entrada. O exemplo testa números perfeitos, um número que não é perfeito, a fronteira 1 e a entrada zero.
A mesma procura de divisores serve para outra decisão: um número primo é um inteiro maior que 1 cujos únicos divisores positivos são 1 e o próprio número.
Para decidir se n é primo, procura um divisor de 2 até n - 1. Encontrar um basta para devolver False; só devolve True depois de excluir todos. O 2 é primo, embora esse percurso seja vazio; o 1 não é primo.
Âmbito das variáveis
O âmbito diz onde um nome pode ser usado. Um nome atribuído dentro de uma função é normalmente local a essa função. A função procura nomes no âmbito local, nos âmbitos envolventes, no módulo e nos nomes predefinidos. Esta procura segue a estrutura do código, o âmbito léxico, não as variáveis locais de quem chamou.
valor = 10
def dobrar(valor):
return 2 * valor
print(dobrar(4), valor)
A saída é 8 10 porque o parâmetro local não altera a associação global. Se atribuíres a valor no corpo, Python trata-o como local nesse corpo. Ler esse local antes de o definir causa UnboundLocalError.
global permite reatribuir um nome do módulo, mas cria uma dependência adicional; prefere parâmetros e retorno para um cálculo.
Uma função pode conservar acesso a nomes de uma função envolvente mesmo depois de essa chamada acabar; esse comportamento, uma closure, está explicado em programação funcional. Por isso, não confundas âmbito com a afirmação de que todos os objetos locais são destruídos no fim da chamada.
Listas, o mínimo necessário
[12, 15] é uma lista com dois números; [] é a lista vazia.
xs.append(9) acrescenta 9 à própria lista e devolve None. Já xs + [9] constrói outra lista, deixando xs intacta. Esta diferença permite observar os efeitos de uma função sobre os dados recebidos.
A lição de tuplos e listas desenvolve as restantes operações e a cópia de listas.
Passagem de argumentos e efeitos
Python associa o parâmetro ao objeto passado, sem o copiar. Reatribuir o parâmetro muda só o nome local:
def tentar_mudar(x):
x = 99
n = 5
tentar_mudar(n)
print(n)
A saída é 5. A chamada associa x a 5 e depois associa x a 99; o nome n continua associado a 5.
Alterar um objeto recebido é diferente de reatribuir o parâmetro. Uma lista permite observar essa diferença: valores.append(99) altera a lista partilhada, enquanto return valores + [99] devolve outra lista. A lição de listas segue as referências num esquema.
Argumentos por nome e valores por defeito
Em def potencia(base, expoente=2):, podes chamar potencia(3) ou potencia(base=3, expoente=4). Um argumento por nome identifica o parâmetro. Os parâmetros obrigatórios anteriores não deixam de ser necessários.
Os valores por defeito são calculados quando def executa, não em cada chamada. Uma lista usada como valor por defeito fica partilhada entre chamadas: cada append altera o mesmo objeto guardado na definição. A lição de listas mostra essa partilha num esquema. Para criar uma lista nova quando não é passada nenhuma, usa None como sinal e cria-a no corpo.
def juntar(valor, valores=None):
if valores is None:
valores = []
return valores + [valor]
print(juntar(1), juntar(2))
A saída é [1] [2]. O is None testa o objeto especial None, que é o uso apropriado de identidade neste caso. O contrato desta função também conserva qualquer lista recebida.
Exercícios
Seguir um parâmetro
Há x = 10 no módulo. Uma função f(x) executa
x = x + 1 e devolve x. Depois de f(3), quanto vale
o x do módulo?
Pista
Mais uma pista
Solução
- 4. 4 é o resultado da chamada, não o valor global.
- 11. A função recebeu 3, e não usou o x do módulo.
- 10. A atribuição altera apenas o parâmetro local.
Erros frequentes
Reatribuir ou alterar
Com xs = [2, 4], uma função executa xs.append(6);
outra executa xs = [8]. Explica o efeito de chamar cada uma
sobre a lista original.
Pista
Mais uma pista
append altera o objeto; uma atribuição muda só o nome local.
Solução
A chamada com append deixa a lista original como
[2, 4, 6]. A função que reatribui o parâmetro não substitui
a lista no chamador. Se a segunda chamada vier depois da primeira, a lista
permanece [2, 4, 6].
Confere a tua resposta:
- Distingui alteração do objeto de reatribuição local.
- Previ o efeito visível no chamador.
- Expliquei que não há cópia automática.
Erros frequentes
Encontrar estado partilhado
Uma função juntar(x, xs=[]) acrescenta x com
append e devolve xs. As chamadas juntar(1) e
depois juntar(2) usam listas diferentes? Explica e propõe uma
correção.
Pista
A lista por defeito é criada quando def executa.
Mais uma pista
Solução
Usam a mesma lista. A primeira chamada deixa [1]; a segunda
acrescenta 2 e devolve [1, 2]. Usa xs=None e
cria uma lista no corpo quando xs is None. Para preservar
também listas recebidas, devolve xs + [x].
Confere a tua resposta:
- Identifiquei a partilha da lista por defeito.
- Previ [1, 2] na segunda chamada.
- Propus
Nonee criação no corpo.
Erros frequentes
Afirmar que [] é avaliado de novo em cada chamada.
Escrever um contrato
Define o contrato de uma função media que recebe uma sequência
de números e devolve a média. Que decisão falta para a entrada vazia?
Pista
Mais uma pista
Solução
O contrato deve exigir uma sequência não vazia, ou especificar que a entrada
vazia levanta uma exceção. Deve dizer que devolve
sum(valores) / len(valores) sem alterar os argumentos. Zero não
é uma média válida por convenção sem que isso seja definido.
Confere a tua resposta:
- Defini o domínio numérico.
- Tratei a sequência vazia explicitamente.
- Indiquei o resultado e a preservação da entrada.
Erros frequentes
Devolver uma decisão
Define e_primo(n), que recebe um inteiro e devolve True exatamente quando n
é primo. Inteiros menores que 2 não são primos. Não leias dados nem imprimas
no corpo da função.
def e_primo(n):
return FalseVerifica o código para comparar o resultado com os testes.
Testes
Não positivos
Código do testeprint(e_primo(-3), e_primo(0))Esperado:False False
Fronteira
Código do testeprint(e_primo(1), e_primo(2))Esperado:False True
Ímpar primo
Código do testeprint(e_primo(17))Esperado:True
Compostos pares e ímpares
Código do testeprint(e_primo(4), e_primo(9), e_primo(25))Esperado:False False False
print(e_primo(-3), e_primo(0))False False
print(e_primo(1), e_primo(2))False True
print(e_primo(17))True
print(e_primo(4), e_primo(9), e_primo(25))False False False
Pista
Trata primeiro os inteiros que ficam fora da definição de primo.
Mais uma pista
Para n >= 2, procura um divisor de 2 até n - 1. Se encontrares um, já
podes devolver False.
Solução
Um divisor encontrado basta para rejeitar o número. O True fica depois do
ciclo, quando todos os divisores foram excluídos. Para 2, o ciclo vazio
chega corretamente a True.
def e_primo(n):
if n < 2:
return False
for divisor in range(2, n):
if n % divisor == 0:
return False
return TrueErros frequentes
Devolver True na primeira iteração sem divisor, testar só a paridade ou
considerar 1 primo.
Devolver em vez de imprimir
Define saudar(nome): recebe uma string e devolve exatamente
“Olá, ” seguida do nome, sem imprimir. Por exemplo,
saudar(“Ana”) deve devolver “Olá, Ana”, sem exclamação.
def saudar(nome):
return ""Verifica o código para comparar o resultado com os testes.
Testes
Devolve texto
Código do testeprint(saudar("Ana"))Esperado:Olá, Ana
Outro nome
Código do testeprint(saudar("Bruno"))Esperado:Olá, Bruno
Reutiliza
Código do testeprint(saudar("Ana") + "!")Esperado:Olá, Ana!
print(saudar("Ana"))Olá, Ana
print(saudar("Bruno"))Olá, Bruno
print(saudar("Ana") + "!")Olá, Ana!
Pista
print escreve; return entrega o valor ao chamador.
Mais uma pista
O teste saudar("Ana") + "!" só passa se a função devolver texto: com print dentro e sem return, o resultado é None e a concatenação falha com TypeError.
Solução
Devolve "Olá, " + nome e imprime apenas fora da função. print devolve
None; uma função que só imprime e chega ao fim sem return também devolve None.
def saudar(nome):
return "Olá, " + nomeErros frequentes
Usar print em vez de return e devolver None; incluir a exclamação dentro da função quando o contrato pede só a saudação.