Informática

Python: funções, escopo, exceções e pegadinhas de prova

Funções, escopo de variáveis e tratamento de erros concentram as pegadinhas mais refinadas de Python em provas: o código parece inocente, e a saída surpreende quem não conhece o modelo da linguagem. Os trechos abaixo foram executados em Python 3 e as saídas são as reais. O básico de tipos e listas está em Python: tipos, listas, dicionários e laços.

Funções, parâmetros e retorno

Uma função é definida com def, recebe parâmetros e devolve valor com return; sem return, devolve None. Parâmetros podem ter valor padrão e podem ser variáveis:

def soma(*args, **kw):
    return sum(args), kw

print(soma(1, 2, 3, a=1))

Saída: (6, {'a': 1}). O *args reúne os argumentos posicionais numa tupla; o **kw reúne os nomeados num dicionário.

Funções anônimas e de ordem superior: list(map(lambda v: v*2, [1, 2, 3])) dá [2, 4, 6]; list(filter(lambda v: v > 1, [1, 2, 3])) dá [2, 3]; sorted(['bb', 'a', 'ccc'], key=len) dá ['a', 'bb', 'ccc']. Um gerador com yield produz valores sob demanda: list(gen(4)), para uma função que cede i*i, dá [0, 1, 4, 9].

Pegadinha 1: parâmetro padrão mutável

def f(x, lista=[]):
    lista.append(x)
    return lista

print(f(1), f(2), f(3))

Saída: [1, 2, 3] [1, 2, 3] [1, 2, 3]. O valor padrão [] é criado uma única vez, quando a função é definida, e é reaproveitado em todas as chamadas. Cada chamada acrescenta à mesma lista. Note que a impressão mostra a lista final três vezes porque print só imprime depois de todas as chamadas, e as três respostas são o mesmo objeto.

A forma correta usa None como sentinela:

def g(x, lista=None):
    if lista is None:
        lista = []
    lista.append(x)
    return lista

print(g(1), g(2)) dá [1] [2].

Pegadinha 2: atribuição não copia

a = [1, 2, 3]
b = a
b.append(4)
print(a, a is b)

Saída: [1, 2, 3, 4] True. b = a copia a referência, não a lista: as duas variáveis apontam para o mesmo objeto. Para copiar, use a.copy(); após c = a.copy(); c.append(5), a continuou [1, 2, 3, 4] e c ficou [1, 2, 3, 4, 5]. A cópia é rasa: listas dentro da lista continuam compartilhadas.

Prova disso com listas aninhadas:

Construção Após m[0][0] = 1
m = [[0]*2]*2 [[1, 0], [1, 0]] (as duas linhas são a mesma lista)
m = [[0]*2 for _ in range(2)] [[1, 0], [0, 0]] (linhas independentes)

Escopo: local, global e a palavra global

Variável criada dentro da função é local. Teste: com x = 10 no módulo e uma função que faz x = 5 e devolve x, a chamada devolve 5, mas o x do módulo continua 10. Para alterar a variável global é preciso declarar global x dentro da função; com isso, x passou a valer 99 no teste. A regra de busca de nomes costuma ser resumida como LEGB: local, englobante (função externa), global e predefinido (built-in).

Recursão

Uma função recursiva chama a si mesma e precisa de um caso base que a encerre.

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

fat(5) dá 120 e fat(0) dá 1. A sequência de Fibonacci com fib(n) = n se n < 2, ou fib(n-1) + fib(n-2), gerou para n de 0 a 9: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34. Sem caso base, a recursão estoura o limite de profundidade da pilha de chamadas (veja a pilha em pilha, fila e lista). A versão ingênua de Fibonacci repete muitos cálculos e tem custo exponencial.

Exceções: try, except, else e finally

try:
    print(1 / 0)
except ZeroDivisionError as e:
    print('erro:', e)
else:
    print('else')
finally:
    print('finally')

Saída: erro: division by zero e depois finally. O else só roda quando não houve exceção; o finally roda sempre, com ou sem erro, o que o torna o lugar certo para liberar recursos.

Exceções comuns e as mensagens obtidas:

Código Exceção Mensagem
int('abc') ValueError invalid literal for int() with base 10: ‘abc’
[1, 2][5] IndexError list index out of range
{'a': 1}['b'] KeyError ‘b’
print(undefined) NameError name ‘undefined’ is not defined
t[0] = 9 (tupla) TypeError ‘tuple’ object does not support item assignment

O except deve citar a exceção mais específica possível; um except genérico esconde erros reais. As exceções formam uma hierarquia, e quem captura uma classe captura também as suas subclasses. Para sinalizar um erro, usa-se raise.

Outras pegadinhas rápidas

  • sorted(l) não altera l; l.sort() altera e devolve None.
  • 3 * 'ab' dá 'ababab'; [0] * 3 dá [0, 0, 0].
  • divmod(17, 5) dá (3, 2): quociente e resto.
  • int(3.9) dá 3 e int(-3.9) dá -3 (truncamento).
  • (1, 2) < (1, 3) é True: tuplas comparam elemento a elemento.

Como analisar um trecho na prova

  1. Liste as variáveis e anote o valor após cada linha.
  2. Marque se cada atribuição cria objeto novo ou apenas outra referência.
  3. Em funções, identifique escopo e valores padrão.
  4. Em try, decida qual bloco executa: except se houve erro, else se não houve, finally sempre.
  5. Releia a pergunta: “o que será impresso” ou “qual o valor final”.

Erros comuns

  • Achar que b = a duplica a lista.
  • Usar lista ou dicionário como valor padrão de parâmetro.
  • Esquecer que variável criada na função não existe fora dela.
  • Achar que o finally só roda em caso de erro.
  • Recursão sem caso base.

Fontes e atualização

Não há norma oficial brasileira sobre a linguagem Python; o conteúdo segue o comportamento documentado do interpretador, e as saídas vieram da execução dos trechos em Python 3. Mensagens de erro e detalhes finos podem mudar entre versões: confira a versão do edital.

Próximos passos

Leia também

Para continuar no site

Viu algo errado, desatualizado ou confuso? Relatar erro nesta página