Pular para o conteúdo

Usando defaultdict no Python para uma lógica de dicionário padrão

[

Usando o tipo defaultdict do Python para lidar com chaves ausentes

Um problema comum que você pode enfrentar ao trabalhar com dicionários em Python é tentar acessar ou modificar chaves que não existem no dicionário. Isso irá gerar um KeyError e interromper a execução do seu código. Para lidar com esse tipo de situação, a biblioteca padrão fornece o tipo defaultdict do Python, uma classe semelhante a um dicionário que está disponível em collections.

O tipo defaultdict do Python se comporta quase exatamente como um dicionário Python regular, mas se você tentar acessar ou modificar uma chave que não existe, o defaultdict criará automaticamente a chave e gerará um valor padrão para ela. Isso torna o defaultdict uma opção valiosa para lidar com chaves ausentes em dicionários.

Neste tutorial, você aprenderá:

  • Como usar o tipo defaultdict do Python para lidar com chaves ausentes em um dicionário.
  • Quando e por que usar um defaultdict em vez de um dicionário regular.
  • Como usar um defaultdict para operações de agrupamento, contagem e acumulação.

Com esse conhecimento, você estará melhor preparado para usar efetivamente o tipo defaultdict do Python em seus desafios de programação diários.

Para aproveitar ao máximo este tutorial, você deve ter algum entendimento prévio sobre o que são dicionários em Python e como trabalhar com eles. Se precisar relembrar, consulte os seguintes recursos:

Tratando chaves ausentes em dicionários

Um problema comum que você pode enfrentar ao trabalhar com dicionários Python é como lidar com chaves ausentes. Se o seu código depende muito de dicionários, ou se você está criando dicionários constantemente, logo perceberá que lidar com exceções frequentes de KeyError pode ser bastante irritante e adicionar complexidade extra ao seu código. Com os dicionários do Python, você tem pelo menos quatro formas disponíveis para lidar com chaves ausentes:

  • Usando o método get()
  • Usando o método setdefault()
  • Usando um bloco try-except
  • Usando o tipo defaultdict

Neste tutorial, nos concentraremos no uso do tipo defaultdict para lidar com chaves ausentes. Vamos começar importando a classe defaultdict do módulo collections:

from collections import defaultdict

Agora você pode criar um defaultdict informando uma função que será usada para gerar o valor padrão sempre que uma chave ausente for acessada:

numbers = defaultdict(int)
print(numbers['one']) # 0

Neste exemplo, estamos criando um defaultdict chamado numbers que usar a função int para gerar o valor padrão. Como a chave ‘one’ não existe no dicionário, o valor padrão gerado pela função int é 0.

Entendendo o tipo defaultdict do Python

O tipo defaultdict do Python é uma subclasse do tipo dict padrão. Ele substitui dois métodos do tipo dict: __missing__() e default_factory. Esses métodos são responsáveis por criar a lógica de gerar valores padrão para chaves ausentes.

O método missing()

O método __missing__() é chamado automaticamente quando uma chave ausente é acessada. Por padrão, o método __missing__() levanta um KeyError, mas você pode substituí-lo para gerar qualquer valor desejado.

Aqui está um exemplo que demonstra como substituir o método __missing__() para retornar a mensagem “Chave ausente”:

class MissingMessageDict(dict):
def __missing__(self, key):
return 'Chave ausente'
messages = MissingMessageDict()
print(messages['hello']) # Chave ausente

Neste exemplo, criamos uma classe MissingMessageDict que herda do tipo dict e substitui o método __missing__() para retornar a mensagem “Chave ausente” sempre que uma chave ausente for acessada no dicionário messages.

O atributo default_factory

O atributo default_factory do tipo defaultdict é uma função que define o valor padrão a ser gerado para chaves ausentes. Por padrão, default_factory é None, o que significa que o defaultdict gera o valor padrão do tipo de dado do objeto. No entanto, você pode atribuir qualquer outra função a default_factory para gerar valores padrão personalizados.

Aqui está um exemplo que mostra como usar o atributo default_factory para gerar uma lista vazia como valor padrão:

fruits = defaultdict(list)
fruits['red'].append('apple')
print(fruits) # {'red': ['apple']}

Neste exemplo, estamos criando um defaultdict chamado fruits que usa a função list como seu default_factory. Ao acessar a chave ‘red’ pela primeira vez, a lista vazia é gerada como valor padrão e o item ‘apple’ é adicionado a essa lista.

usando o método setdefault()

Aqui está um exemplo que mostra a diferença entre usar setdefault() em um dicionário regular e em um defaultdict:

regular_dict = {}
regular_dict.setdefault('one', []).append(1)
print(regular_dict) # {'one': [1]}
default_dict = defaultdict(list)
default_dict.setdefault('one', []).append(1)
print(default_dict) # defaultdict(<class 'list'>, {'one': [1]})

Neste exemplo, estamos usando setdefault() para inserir a chave ‘one’ com uma lista vazia como valor padrão. No regular_dict, a lista vazia é inserida manualmente usando setdefault(). No default_dict, a lista vazia é gerada automaticamente graças ao uso do default_factory.

Comparando defaultdict e dict

Em termos de funcionalidade, o defaultdict é semelhante a um dicionário regular. No entanto, há uma diferença sutil nos comportamentos quando um dicionário regular e um defaultdict são acessados com uma chave ausente.

Quando você tenta acessar uma chave inexistente em um dicionário regular, um KeyError é gerado:

regular_dict = {}
print(regular_dict['one']) # KeyError: 'one'

No caso de um defaultdict, a chave é criada automaticamente e um valor padrão é gerado:

default_dict = defaultdict(int)
print(default_dict['one']) # 0

Essa diferença de comportamento pode ser conveniente em situações onde você precisa lidar com chaves ausentes de forma transparente e evitar erros de KeyError.

Emulando o tipo defaultdict do Python

Se você estiver trabalhando com versões mais antigas do Python que não possuem o defaultdict embutido, é possível emular seu comportamento com um dicionário regular e um pouco de lógica adicional.

Aqui está um exemplo de como emular um defaultdict usando um dicionário regular e a função setdefault():

fruits = {}
fruits.setdefault('red', []).append('apple')
print(fruits) # {'red': ['apple']}

Neste exemplo, estamos emulando um defaultdict usando um dicionário regular chamado fruits e a função setdefault(). A lista vazia é inserida manualmente como valor padrão usando setdefault(), de forma semelhante ao comportamento de um defaultdict.

No entanto, a emulação de um defaultdict não é tão eficiente quanto o uso do defaultdict nativo. O defaultdict incorpora a lógica de geração de valores padrão diretamente, otimizando a performance e a legibilidade do código.

Passando argumentos para .default_factory

Você pode passar argumentos adicionais para a função default_factory quando estiver criando um defaultdict. Isso permite que você customize ainda mais a lógica de geração de valores padrão para suas necessidades específicas.

Usando lambda

Uma maneira comum de passar argumentos personalizados para a função default_factory é usando a expressão lambda. Com lambda, você pode criar funções anônimas em uma única linha.

Aqui está um exemplo que mostra como usar lambda para criar um defaultdict que gera listas vazias como valores padrão para diferentes tipos de frutas:

fruits = defaultdict(lambda: [])
fruits['red'].append('apple')
fruits['yellow'].append('banana')
print(fruits) # {'red': ['apple'], 'yellow': ['banana']}

Neste exemplo, estamos usando lambda para criar uma função anônima que retorna uma lista vazia. Essa função é usada como default_factory do defaultdict chamado fruits. Dessa forma, sempre que uma chave ausente for acessada no fruits, uma lista vazia será gerada como valor padrão.

Usando functools.partial()

Outra maneira de passar argumentos personalizados para a função default_factory é usando a função functools.partial(). A função partial() permite criar uma nova função a partir de uma função existente, pré-configurando os argumentos dessa função.

Aqui está um exemplo que mostra como usar functools.partial() para criar um defaultdict que gera dicionários vazios como valores padrão:

from collections import defaultdict
from functools import partial
dict_factory = partial(defaultdict, dict)
nested_dict = defaultdict(dict_factory)
nested_dict['key1']['subkey'] = 'value'
print(nested_dict) # {'key1': {'subkey': 'value'}}

Neste exemplo, estamos usando functools.partial() para criar uma função dict_factory que chama defaultdict com a função dict como seu default_factory. Essa função é usada como default_factory do defaultdict chamado nested_dict. Dessa forma, sempre que uma chave ausente for acessada no nested_dict, um dicionário vazio será gerado como valor padrão.

Conclusão

Neste tutorial, exploramos o tipo defaultdict do Python e como ele pode ser útil para lidar com chaves ausentes em dicionários. Vimos como usar um defaultdict para agrupar itens, contar itens e acumular valores. Também aprendemos as diferenças entre um defaultdict e um dicionário regular, e como emular o comportamento do defaultdict em versões mais antigas do Python.

Ao usar o tipo defaultdict, você pode simplificar o tratamento de chaves ausentes em dicionários, tornando seu código mais legível e eficiente. Agora que você tem esse conhecimento, sinta-se à vontade para explorar ainda mais os recursos do defaultdict e encontrar maneiras criativas de aplicá-lo em seus próprios projetos Python.