Cuidado com o Python dict.get ()

Pierre de Wulf Blocked Desbloquear Seguir Seguindo 5 de janeiro

Se você acha que value = my_dict.get('my_key', 'default_value') é equivalente a value = my_dict.get('my_key') or 'default_value' você provavelmente deveria ler isto ?. Se você sabe por que não é o mesmo, então provavelmente você não aprenderá algo aqui.

A coisa boa:

Como qualquer um usando o Python 3 deve saber, a API do dict é muito clara e simples. Eu posso declarar um dict assim:

 my_car = {'wheels': 4, 'brand': 'Tesla'} 

É simples, rápido e fácil. Recuperar valores é tão fácil:

 my_car.get ('marca') 
>>> 'Tesla'
my_car ['brand']
>>> 'Tesla'

Mas, para recuperar valores, prefiro o .get() por dois motivos. Primeiro, não haverá exceções levantadas se a chave que você deseja acessar não estiver aqui (ela retornará None ). Segundo, você pode passar um valor padrão para o método que será retornado se a chave não estiver presente no dict :

 my_car ['color'] 
>>> KeyError: 'cor'
 my_car.get ('cor') 
>>>
 my_car.get ('color', 'black') 
>>> 'preto'

E o complicado:

Agora eu vou mostrar o que aconteceu no mundo real enquanto consertava um bug para ShopToList em um método que eu escrevi que usa uma biblioteca que extrai metadados de uma página HTML (neste caso uma página de e-commerce).

Para tornar as coisas curtas, os dados que eu esperava devem ficar assim (exemplo simplificado):

 data_from_extruct = { 
'title': 't-shirt',
'marca': 'francês-foguete',
'cor verde',
'oferta': {
'quantia': 20,
'moeda': '€'
}
}

A maneira mais fácil de obter o preço desses dados é:

 price_from_extruct = data_from_extruct ['offer'] ['amount'] 
>>> 20

Mas como eu disse antes, esta solução não é nada robusta. Este é o mundo real, e no mundo real os dados do extrato nem sempre vêm com uma oferta e com um preço nessa oferta. Uma maneira melhor de fazer isso é usar o dict.get :

 price_from_extruct = data_from_extruct.get ('offer'). get ('amount') 

Isso ainda não é bom o suficiente porque, se não houver offer nos dados, você tentará executar o segundo .get('amount') em None e ele gerará um erro. Uma maneira de evitar isso é fazer:

 price_from_extruct = data_from_extruct.get ('offer', {}). get ('amount') 

Aqui, se não tivermos offer nos dados, o primeiro get retornará {} (empty dict) vez de None e, em seguida, o segundo get será executado contra um dict vazio e retornará None . Tudo é ótimo, parece que temos uma maneira robusta de extrair o preço dos dados que não são consistentemente formatados. É claro que às vezes o valor não será none mas pelo menos esse código nunca deve quebrar.

Bem, estamos errados. A captura vem do comportamento do parâmetro padrão. Lembre-se que o valor padrão será retornado se, e somente se, a chave estiver ausente do dict.

O que isso significa é que, se os dados recebidos forem assim:

 data_from_extruct = { 
'title': 't-shirt',
'marca': 'francês-foguete',
'cor verde',
'oferta': nenhuma
}

Em seguida, o snippet anterior será interrompido:

 price_from_extruct = data_from_extruct.get ('offer', {}). get ('amount') 
>>> AttributeError: objeto 'NoneType' não tem atributo 'get'

Aqui o valor padrão de get('offer', {}) não foi retornado porque a offer chave estava no dict. Foi ajustado apenas para None .

Claro Python é incrível, então há muita maneira simples de corrigir isso. O trecho a seguir é apenas um deles:

 offers_from_extruct = data_from_extruct.get ('offer') ou {} 
price_from_extruct = offers_from_extruct.get ('amount')

Claro, isso também pode quebrar se o conteúdo da offer for uma lista, por exemplo. Mas por causa do exemplo, vamos parar por aqui.