Pular para o conteúdo
Ramos da Informática - Comunidade de Desenvolvedores

Torne-se um desenvolvedor FullStack: Pacote completo de formação desenvolvedor Frontend e Backend utilizando as linguagens de programação e frameworks mais procurados no mercado de trabalho. Mais informações, aqui. Faça o download do E-BookGuia Completo Para Se Tornar um(a) Desenvolvedor(a) Full-Stack, Começando do ZERO”.

Engenheiro de Software, autor de livros sobe tecnologia e negócios. É mantenedor do site Ramos da Informática. Hobbies: investir em ações, natação e finanças.

Engenheiro de Software, autor de livros sobe tecnologia e negócios. É mantenedor do site Ramos da Informática. Hobbies: investir em ações, natação e finanças.

admin

Todos os artigos deste autor

A armadilha dos argumentos com valores padrão

Chatbots com Whatsapp e Cielo integrados Nesse curso, eu vou te mostrar como o consumidor poder realizar um pagamento por dentro do aplicativo do WhatsApp, aonde o seu cliente vai entrar numa conversa como entraria numa conversa com qualquer pessoa ou com a sua empresa, navegar entre os produtos/serviços em menus simples enviados pelo chatbot, adicionar esses produtos/serviços no carrinho de compras, e num determinado ponto do chat, um link exclusivo é enviado para o cliente preencher os dados do cartão de crédito. No final, a análise é devolvida para o Whatsapp no qual a conversa foi iniciada. Inscreva-se.

Algo muito comum em várias linguagens de programação é a possibilidade de definir valores default (valores padrão) para argumentos de funções e métodos, tornando a utilização desses opcional.

Isso é ótimo, principalmente para manter retrocompatibilidade, porém, o python possui uma pequena armadilha que caso passe despercebida, pode causar sérios problemas, muitas vezes difíceis de serem detectados.

Essa armadilha ocorre quando usamos valores de tipos mutáveis como valor default de argumentos.

O que são tipos mutáveis e imutáveis?

Segundo a documentação oficial do python, o valor de alguns objetos pode mudar, esses objetos que podem ter seu valor alterado após serem criados são chamados de mutáveis, enquanto que os objetos que não podem ter seus valores alterados após serem criados são chamados de imutáveis (simples assim).

Tipos mutáveis:

Listas, Dicionários e tipos definidos pelo usuário.

Tipos imutáveis:

Numeros, Strings e Tuplas.

Apesar de serem imutáveis, a utilização de um valor mutável (uma lista por exemplo) dentro de uma tupla, pode causar o efeito tuplas mutáveis, onde visualmente o valor da tupla é alterado, mas por trás dos panos o valor da tupla não muda, o que muda é o valor do objeto pelo qual a tupla está se referenciando.

A armadilha

Como disse no começo desse blogpost, é muito comum a utilização de valores default em agurmentos de funções e métodos, por essa razão, nos sentimos seguros em fazer algo desse tipo:

def my_function(my_list=[]):
my_list.append(1)
print(my_list)

Porém, levando esse exemplo em consideração, o que irá acontecer se invocarmos essa função 3 vezes?

>>> my_function()
[1]
>>> my_function()
[1, 1]
>>> my_function()
[1, 1, 1]

Sim, o valor do argumento my_list mudou em cada vez que executamos a função sem passar algum valor para ele.

Por que isso acontece?

Isso acontece porque o python processa os valores default de cada argumentos de uma função (ou método) quando essa for definida, após esse processamento o valor é atribuido ao objeto da função.
Ou seja, por questões de optimização, seguindo nosso exemplo, o python não cria uma lista vazia para o argumento my_list a cada vez que a função my_function for invocada, ele reaproveita uma lista que foi criada no momento em que essa função foi importada.

>>> my_function.func_defaults
([],)
>>> id(my_function.func_defaults[0])
140634243738080
>>> my_function()
[1]
>>> my_function.func_defaults
([1],)
>>> id(my_function.func_defaults[0])
140634243738080
>>> my_function()
[1, 1]
>>> my_function.func_defaults
([1, 1],)
>>> id(my_function.func_defaults[0])
140634243738080

Note que a identificação do argumento (no caso my_list) não muda, mesmo executando a função várias vezes.

Outro exemplo seria utilizar o resultado de funções como valores default de argumentos, por exemplo, uma função com um argumento que recebe como default o valor de datetime.now().

def what_time_is_it(dt=datetime.now()):
print(dt.strftime(%d/%m/%Y %H:%M:%S’))

O valor do argumento dt sempre será o datetime do momento em que o python carregou a função e não o datetime de quando a função foi invocada.

>>> what_time_is_it()
07/06/2015 08:43:55
>>> time.sleep(60)
>>> what_time_is_it()
07/06/2015 08:43:55

Isso também acontece com classes?

Sim e de uma forma ainda mais perigosa.

class ListNumbers():
def __init__(self, numbers=[]):
self.numbers = numbersdef add_number(self, number):
self.numbers.append(number)

def show_numbers(self):
print(numbers)

Assim como no caso das funções, no exemplo acima o argumento numbers é definido no momento em que o python importa a classe, ou seja, a cada nova instância da classe ListNumbers, será aproveitada a mesma lista no argumento numbers.

>>> list1 = ListNumbers()
>>> list2 = ListNumbers()
>>> list1.show_numbers()
[]
>>> list2.show_numbers()
[]
>>> list2.add_number(1)
>>> list1.show_numbers()
[1]
>>> list2.show_numbers()
[1]
>>> list1.numbers is list2.numbers
True

Por que isso não acontece com Strings?

Porque strings são imutáveis, o que significa que a cada alteração de valor em uma variavel que armazena uma strings, o python cria uma nova instância para essa variável.

>>> a = ‘foo’
>>> id(a)
140398402003832
>>> a = ‘bar’
>>> id(a)
140398402003872 # o penúltimo número muda 🙂

Em argumentos com valores default, não é diferente.

def my_function(my_str=’abc’):
my_str += ‘d’
print(my_str)

No exemplo acima, sempre que for executado o inplace add (+=) será criada outra váriavel para my_str sem alterar o valor default do argumento.

>>> my_function()
abcd
>>> my_function.func_defaults
(‘abc’,)
>>> my_function()
abcd
>>> my_function.func_defaults
(‘abc’,)

Como se proteger?

A maneira mais simples de evitar esse tipo de surpresa é utilizar um valor sentinela como por exemplo None, nos argumentos opcionais que esperam tipos mutáveis:

def my_function(my_list=None):
if my_list is None:
my_list = []
my_list.append(1)
print(my_list)

Ou, para deixar o código ainda mais elegante, podemos simplificar a condicional com um simples or:

def my_function(my_list=None):
my_list = my_list or []
my_list.append(1)
print(my_list)

Obrigado Bruno Rocha pela sugestão.

Pronto, sem surpresas e sem armadilhas :).

>>> my_function()
[1]
>>> my_function()
[1]
>>> my_function()
[1]

Referências

Fluent Python (Mutable types as parameter defaults: bad idea)
Python Anti-Patterns (Using a mutable default value as an argument)

Este artigo foi importado automaticamente por fazer parte do Planetário Dev. Quer fazer parte deste HUB de conteúdos? Faça parte do Planetário e veja as vantagens.

Não tem site ou blog? Seja um autor do site e ainda pode ser remunerado.

Leia também:

Facebook
LinkedIn
Twitter
Pinterest
Reddit
Telegram
WhatsApp
Email
Print

Relacionados

Deixe uma resposta