Módulo 3 - Funções avançadas

A armadilha do padrão mutável

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Reproduzir o bug do argumento padrão mutável e entender a causa.
  • Compreender que o valor padrão é criado uma vez, na definição.
  • Aplicar a solução com None para criar um novo objeto a cada chamada.
  • Reconhecer o padrão de correção em qualquer função que aceite lista ou dicionário.

O bug clássico, ao vivo

Este é, talvez, o erro mais famoso de quem passa do básico para o intermediário em Python. A intenção é boa: você quer uma função que receba uma lista, e, se ninguém passar, comece com uma lista vazia. Parece natural escrever lista=[] como padrão. O problema é sutil e cruel: esse [] é avaliado uma única vez, no momento em que a função é definida, e o mesmo objeto vazio passa a ser reaproveitado em toda chamada que não informar a lista. Como listas são mutáveis, o que uma chamada acrescenta fica lá para a próxima.

# Errado: lista mutavel como valor padrao
def adicionar(item, lista=[]):
    lista.append(item)
    return lista

print(adicionar("a"))  # ['a']
print(adicionar("b"))  # ['a', 'b']  <- deveria ser so ['b']!
print(adicionar("c"))  # ['a', 'b', 'c']  <- o lixo se acumula
# A mesma lista e reaproveitada entre chamadas: bug silencioso.

O padrão [] é criado uma vez e compartilhado: cada chamada herda o que a anterior deixou.

Leia a saída com atenção. A segunda chamada deveria devolver apenas a letra b, porque não passamos nenhuma lista. Em vez disso, ela devolve a e b, porque reaproveitou a mesma lista da primeira chamada. Na terceira, o acúmulo continua. O código não deu erro nenhum; ele simplesmente fez algo diferente do que você imaginou. Esse tipo de bug silencioso é o pior, porque passa nos testes rápidos e explode mais tarde, em produção, com dados estranhos que ninguém entende de onde vieram.

Por que isso acontece

A raiz do problema é uma regra do Python que você viu na primeira aula deste módulo: o valor padrão é calculado uma única vez, quando a definição da função é lida, e não a cada chamada. Para um número ou um texto isso não faz diferença, porque eles são imutáveis: ninguém consegue alterar o número 5 ou o texto vazio por dentro. Já uma lista é mutável. O append muda o próprio objeto, e como esse objeto é sempre o mesmo padrão criado lá no começo, a mudança persiste entre chamadas.

Padrão imutável (seguro)

  • saudacao="Olá", contador=0, ativo=True
  • O valor não pode ser alterado por dentro
  • Nada vaza de uma chamada para a outra
  • Pode usar direto, sem cuidado extra

Padrão mutável (armadilha)

  • lista=[], config={}, itens=set()
  • O objeto é alterado por append, update, add
  • O mesmo objeto é compartilhado entre chamadas
  • Precisa da solução com None

A solução: None como sentinela

A correção é simples e vira reflexo com o tempo. Em vez de usar a lista vazia como padrão, use None, que é imutável e serve de sinal de que nada foi passado. Dentro da função, um if verifica: se o argumento é None, crie uma lista nova ali mesmo. Como a criação passa a acontecer a cada chamada, cada uma ganha a sua própria lista, e o vazamento acaba. Esse padrão, chamado de sentinela None, funciona para listas, dicionários, conjuntos e qualquer outro objeto mutável.

# Certo: None como padrao, cria a lista dentro
def adicionar(item, lista=None):
    if lista is None:
        lista = []
    lista.append(item)
    return lista

print(adicionar("a"))  # ['a']
print(adicionar("b"))  # ['b']  <- isolado, como esperado
print(adicionar("c"))  # ['c']  <- cada chamada tem sua lista

Com None e o if, cada chamada cria a própria lista. O bug desaparece.

Compare as duas saídas lado a lado: agora cada chamada devolve só o seu item, sem herdar nada da anterior. A diferença é uma linha, o if lista is None, mas ela muda tudo. Use is None, com o operador is, e não == None: é a forma idiomática de comparar com None em Python. Guardando esse padrãozinho, você atravessa toda a carreira sem cair na armadilha mais clássica da linguagem, aquela que quase todo programador Python já encontrou pelo menos uma vez.

Teste rápido

Por que usar lista=None em vez de lista=[] como valor padrão?

Perguntas frequentes

Esse problema acontece só com listas?
Não. Acontece com qualquer objeto mutável usado como padrão: listas, dicionários, conjuntos e até objetos das suas próprias classes. Todos são criados uma vez e compartilhados entre chamadas. Números, textos, tuplas, True, False e None são imutáveis e não causam o problema, por isso são padrões seguros.
Por que usar is None e não == None?
None é único no programa, então a comparação de identidade com is é a forma idiomática e recomendada em Python, inclusive pela PEP 8. Além de mais correta em intenção, evita surpresas com objetos que redefinem o operador de igualdade. Escreva sempre if x is None e if x is not None.
Dá para ver que a lista padrão é sempre a mesma?
Dá. Você pode inspecionar adicionar.__defaults__, que mostra os valores padrão guardados na função. Depois de algumas chamadas com o código errado, você verá que a lista lá dentro já não está vazia, prova de que é sempre o mesmo objeto sendo reaproveitado.
E se eu realmente quiser manter estado entre chamadas?
Aí o certo é ser explícito, não depender de um efeito colateral obscuro do padrão mutável. Use uma variável fora da função, um argumento que você mesmo passa, ou uma classe que guarde o estado (módulo 10). Manter estado por acidente, via padrão mutável, é fonte de bug, não uma técnica.
O editor ou o Python me avisam desse erro?
O Python não dá erro, porque o código é válido. Mas ferramentas de análise, como linters (por exemplo, o Ruff ou o Pylint), sinalizam argumento padrão mutável com um aviso específico. Vale rodar um linter no projeto justamente para pegar armadilhas silenciosas como esta antes que virem bug.
Preciso do if None mesmo quando quase sempre passo a lista?
Sim, sempre que o parâmetro tiver padrão mutável. Basta uma única chamada sem informar a lista para o compartilhamento começar. O custo do if lista is None é uma linha; o custo de esquecê-lo é um bug intermitente e difícil de rastrear. É barato fazer certo desde o início.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.