5 de agosto de 2009

Pegadinha: parâmetro default

Toda linguagem tem suas armadilhas e Python não é uma excessão.

A linguagem é prática, clara e objetiva, mas nem tudo é perfeito.

A seção Programming FAQ do General Python FAQ fala sobre esse assunto, mas passa despercebido para iniciantes na linguagem. É o tipo de informação que quem está começando não dá muita atenção, o que é normal mesmo. Deixa para aprender depois e às vezes se depara com um problema que é chato de resolver, mas simples.

Não vou traduzir aqui o que o FAQ diz, mas vou transcrever um trecho e enfatizar o que é dito ali:

It is often expected that a function call creates new objects for default values. This is not what happens. Default values are created exactly once, when the function is defined. If that object is changed, like the dictionary in this example, subsequent calls to the function will refer to this changed object.
By definition, immutable objects such as numbers, strings, tuples, and None, are safe from change. Changes to mutable objects such as dictionaries, lists, and class instances can lead to confusion.
Podemos ler que os valores default não são criados no momento da chamada da função, mas sim no momento da definição. Isso faz toda a diferença. O texto continua explicando o porquê: "Se o objeto é modificado, como o dicionário mostrado no exemplo, as chamadas seguintes à essa função farão referência ao objeto modificado" e não o criará vazio novamente, como seria de esperar (!!).
No parágrafo seguinte, vemos a importância de sabermos quais objetos são mutáveis e imutáveis em Python: números, strings, tuplas e None. Com esses você pode usar os parâmetros default com segurança. Caso contrário, fuja disso.
Quer saber até onde essa situação pode chegar? Veja um exemplo simples abaixo:

class SimpleTest:
    k = ''
    v = ''
    def one_more(self, d={}):
        d[self.k] = self.v
        return d

a = SimpleTest()
a.k = 'name'
a.v = 'John Smith'
da = a.one_more()
print da

b = SimpleTest()
b.k = 'phone'
b.v = '888-7766'
db = b.one_more()
print db

Execute no seu shell de Python e veja o retorno. As variáveis da e db compartilham o mesmo objeto! E não é isso o que queremos.

Eu, por precaução, resolvi não adotar os parâmetros default para nenhum tipo de objeto, mas essa é uma escolha pessoal.
Os anos de estrada me ensinaram a ficar longe das armadilhas. De vez em quando uma delas pode te pegar distraído! ;-)

[update] Segue um update importante, baseado no comentário do Vinícius Mendes:

A forma correta de aceitar parâmetros default no exemplo acima é:
class SimpleTest:
    k = ''
    v = ''
    def one_more(self, d=None):
        if d is None:
           d = {}
        d[self.k] = self.v
        return d

Aliás, essa é uma boa prática em Python: receber os parâmetros default como None e tratá-los no início da função:
def funcao(a=None, b=None):
    if a is None:
      a = ''
    if b is None:
      b = 0

    # ...

Uma forma reduzida, e muito mais usada, é:
def funcao(a=None, b=None):
    a = a or ''
    b = b or 0
    # ...

Eu sou Vinicius Assef, um programador do século passado que gosta de Python, pratica Lean Development e acredita em Deus. Você pode me contactar por email ou twitter.

Um comentário:

  1. Eu normalmente coloco o valor default como None, em seguida, testo se é None, se for, eu atribuo o valor default real.

    ResponderExcluir

Marcadores