Desvendando a Armadilha dos Argumentos Mutáveis como Default em Python

Você já enfrentou resultados inesperados ao usar listas ou dicionários como valores padrão em funções Python? Esse é um problema comum que pode causar bugs sutis e difíceis de encontrar. Neste artigo técnico, vamos desmistificar o motivo desse comportamento, mostrando exemplos práticos e como evitá-lo com boas práticas de programação. Se você é um desenvolvedor Python buscando produzir código mais robusto e previsível, este conteúdo é essencial para o seu dia a dia.

Evitando Surpresas

Ao definir funções em Python, usar valores padrão em argumentos é comum para facilitar chamadas. Contudo, quando o valor padrão é um tipo mutável, como listas ou dicionários, isso pode causar efeitos inesperados. Vamos analisar exemplos para entender esse comportamento.

Exemplo Problemático: Lista Mutável como Valor Padrão

def add_item(item, lista=[]):
    lista.append(item)
    return lista

print(add_item('maçã'))    # Saída esperada: ['maçã']
print(add_item('banana'))  # Saída inesperada: ['maçã', 'banana']

Aqui, o segundo print adiciona o item 'banana' à mesma lista usada na primeira chamada, porque o objeto lista padrão foi criado uma vez e reutilizado. Isso acontece porque argumentos padrão são avaliados apenas na definição da função.

Corrigindo com None como Valor Padrão

A forma recomendada é usar None e criar a lista dentro da função quando necessário:

def add_item(item, lista=None):
    if lista is None:
        lista = []
    lista.append(item)
    return lista

print(add_item('maçã'))    # Saída: ['maçã']
print(add_item('banana'))  # Saída: ['banana']

Assim, cada chamada sem lista passa a criar uma nova lista vazia, evitando efeitos colaterais.

Mais um Exemplo com Dicionário Mutável

def increment_count(key, counts={}):
    counts[key] = counts.get(key, 0) + 1
    return counts

print(increment_count('python'))  # {'python': 1}
print(increment_count('java'))    # {'python': 1, 'java': 1} – resultado inesperado

No caso acima, o dicionário padrão é compartilhado e mantido entre as chamadas. A forma correta:

def increment_count(key, counts=None):
    if counts is None:
        counts = {}
    counts[key] = counts.get(key, 0) + 1
    return counts

print(increment_count('python'))  # {'python': 1}
print(increment_count('java'))    # {'java': 1}

Exemplo com Classe e Argumento Mutável

class Collector:
    def __init__(self, items=[]):
        self.items = items

    def add(self, item):
        self.items.append(item)

    def get_items(self):
        return self.items

c1 = Collector()
c1.add('foo')

c2 = Collector()
print(c2.get_items())  # Saída inesperada: ['foo']

A mesma lista é compartilhada por todas as instâncias quando passada como valor padrão mutável. Correção:

class Collector:
    def __init__(self, items=None):
        self.items = items or []

    def add(self, item):
        self.items.append(item)

    def get_items(self):
        return self.items

c1 = Collector()
c1.add('foo')

c2 = Collector()
print(c2.get_items())  # Saída correta: []

Boas Práticas para Evitar Problemas com Argumentos Mutáveis

Dominar essas técnicas ajuda a produzir código Python mais resiliente e menos propenso a bugs difíceis de detectar, essencial para engenheiros de software que buscam qualidade profissional em seus projetos.



Riverfount
Vicente Eduardo Ribeiro Marçal