Pegadinha em valores default em funções

Se você é desenvolvedor Python, já deve ter visto muitas funções que definem valores padrão para alguns de seus argumentos. Por exemplo:

def power(base, exp=2):
    return base**exp

É bem comum usarmos esse artifício quando queremos que determinado parâmetro tenha um valor mesmo que o chamador não tenha passado valor algum para ele. Isso funciona muito bem, porém, pode gerar uma certa confusão quando o tipo do valor default do parâmetro em questão for mutável. Veja o trecho de código abaixo:

def func(valor, seq=[]):
    seq.append(valor)
    return seq

print func(10)
print func(20)
print func(30)

Antes de executar o código acima, responda: o que será impresso pelo código acima?

Se você respondeu

[10]
[20]
[30]

você está errado, pois o resultado é:

[10]
[10, 20]
[10, 20, 30]

Observe que, na segunda chamada à função func(), a lista seq manteve o valor 10 como elemento e então adicionou o valor 20 ao seu final. Mas por quê, se seq possui uma lista vazia [] como valor default? Este valor não deveria ser atribuído a seq a cada chamada de função?

A resposta é, como tudo em Python, consistente com a linguagem. Em Python, valores default de parâmetros são avaliados somente no momento em que a função estiver sendo avaliada (em sua definição), e não a cada chamada à mesma.

Ao encontrar a palavra-chave def, o interpretador Python avalia a expressão seguinte como uma função e então cria em memória um objeto function referente à função definida. Assim, a atribuição seq=[] é feita pelo interpretador nesse momento, e não a cada vez que func for chamada.

Vamos ver o que acontece: quando func é chamada pela primeira vez, a lista referenciada por seq possui o valor []. Então, dentro da função é feito um append em seq do valor recebido como parâmetro. Como seq é uma referência para um objeto mutável (uma lista), a lista referenciada por seq é quem tem adicionada a si o valor passado como parâmetro. Na chamada seguinte, seq continua apontando para o mesmo objeto lista, que agora possui um elemento (o valor 10) e então o valor 20 será, dentro da função, adicionado ao final da lista referenciada por seq. Na última chamada, é adicionado o valor 30 ao final da lista apontada por seq.

O que deve ser lembrado sempre é que valores default para parâmetros em uma função são avaliados somente na definição da mesma, e não a cada chamada.

Como driblar isso?

Se você precisar que um parâmetro tenha o valor [] quando o chamador não passar valor algum a ele, você pode fazer o seguinte:


def func(valor, seq=None):
    if seq is None:
        # chamador não forneceu valor para seq
        seq = []
    seq.append(valor)
    return seq

Leia mais

Detalhes como o apresentado neste post são tratadas em alguns livros, como os excelentes:

Obrigado ao Elias Dorneles pela revisão!

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s