Pular para o conteúdo

Como usar o defaultdict do Python de forma fácil?

CodeMDD.io

Usando o tipo defaultdict do Python para lidar com chaves ausentes

por seudomínio do autor

Uma problema comum que pode surgir ao trabalhar com dicionários em Python é tentar acessar ou modificar chaves que não existem no dicionário. Isso resultará em um KeyError e interromperá a execução do código. Para lidar com esse tipo de situação, a biblioteca padrão do Python fornece o tipo defaultdict, 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 comum, mas se você tentar acessar ou modificar uma chave ausente, 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.
  • Como usar um defaultdict para agrupar , contar e acumular valores.

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

Para aproveitar ao máximo este tutorial, é necessário ter algum conhecimento prévio sobre o que são dicionários em Python e como trabalhar com eles. Se você precisar refrescar a memória, consulte os seguintes recursos:

Bônus gratuito: Clique aqui para obter um Python Cheat Sheet e aprender o básico do Python 3, como trabalhar com tipos de dados, dicionários, listas e funções Python.

Lidando com chaves ausentes em dicionários

  1. Usando try…except:
my_dict = {"a": 1, "b": 2}
try:
value = my_dict["c"]
except KeyError:
value = 0
  1. Usando o método get():
my_dict = {"a": 1, "b": 2}
value = my_dict.get("c", 0)
  1. Usando o módulo collections.defaultdict:
from collections import defaultdict
my_dict = defaultdict(int)
value = my_dict["c"]
  1. Definindo um valor padrão antes de acessar o dicionário:
my_dict = {"a": 1, "b": 2}
value = my_dict.setdefault("c", 0)

Embora todas essas abordagens possam funcionar, o uso do tipo defaultdict do Python oferece uma alternativa mais elegante e eficiente para lidar com chaves ausentes em dicionários. Vamos explorar mais sobre o defaultdict e como usá-lo de maneira eficaz.

Entendendo o tipo defaultdict do Python

O tipo defaultdict do Python, que está disponível no módulo collections, é uma subclasse do tipo dict padrão do Python. Ao contrário de um dicionário comum, o defaultdict requer um argumento no momento da criação, chamado de default_factory. Esse argumento pode ser uma função ou uma classe que será chamada para gerar um valor padrão sempre que uma chave ausente for acessada.

Internamente, o defaultdict armazena a função default_factory em um atributo chamado do defaultdict.default_factory, que é uma função chamada sempre que uma chave ausente é solicitada. Essa função pode retornar qualquer valor e será usada para criar a chave ausente no defaultdict.

O exemplo a seguir mostra como criar um defaultdict com uma função default_factory que retorna uma string vazia sempre que uma chave ausente é acessada:

from collections import defaultdict
my_dict = defaultdict(str)
value = my_dict["key"]
print(value) # Output: ""

Nesse exemplo, my_dict["key"] retorna uma string vazia "" em vez de lançar um KeyError, porque a função str é a função default_factory definida para o defaultdict. No momento em que a chave “key” é acessada e não é encontrada, a função str é chamada, e seu valor de retorno é usado como o valor padrão para a chave “key” no defaultdict.

É importante ressaltar que a função default_factory é chamada apenas para as chaves ausentes que não podem ser encontradas no defaultdict. Se uma chave existente for acessada, o defaultdict se comportará como um dicionário normal, retornando o valor correspondente à chave.

Usando o tipo defaultdict do Python

Agora que você entende como o tipo defaultdict do Python funciona, vamos explorar algumas maneiras de usá-lo para lidar com chaves ausentes em dicionários.

Agrupando itens

Suponha que você tenha uma lista contendo vários itens e queira agrupar esses itens por uma determinada propriedade. Usando um defaultdict, você pode simplificar esse processo de agrupamento. Por exemplo, vamos agrupar uma lista de nomes por sua primeira letra:

from collections import defaultdict
names = ["Alice", "Bob", "Charlie", "Anne", "Mary", "John"]
name_groups = defaultdict(list)
for name in names:
first_letter = name[0]
name_groups[first_letter].append(name)
print(name_groups)

A saída do código acima será um defaultdict que agrupa os nomes por sua primeira letra:

defaultdict(<class 'list'>, {'A': ['Alice', 'Anne'], 'B': ['Bob'], 'C': ['Charlie'], 'M': ['Mary'], 'J': ['John']})

Observe que a função default_factory definida para o defaultdict é list, o que faz com que cada chave ausente seja automaticamente associada a uma lista vazia. Assim, quando o loop for percorrido e um nome for processado, a primeira letra do nome será usada como chave para acessar um item de lista no defaultdict. Se a chave não existir, uma lista vazia será criada automaticamente e o nome será adicionado a essa lista. Dessa forma, todos os nomes com a mesma primeira letra serão agrupados corretamente no defaultdict.

Agrupamento de itens únicos

Além de agrupar todos os itens, você também pode agrupar apenas os itens únicos em um defaultdict.

from collections import defaultdict
names = ["Alice", "Bob", "Charlie", "Anne", "Mary", "John"]
unique_name_groups = defaultdict(set)
for name in names:
first_letter = name[0]
unique_name_groups[first_letter].add(name)
print(unique_name_groups)

Nesse exemplo, a função default_factory é set. Isso permite que cada chave ausente seja automaticamente associada a um set vazio. Durante o loop, cada nome é adicionado ao conjunto associado à chave correspondente à primeira letra do nome. Como um conjunto não permite itens repetidos, apenas os nomes únicos serão mantidos no defaultdict ao final do loop.

Contagem de itens

Outra tarefa comum ao lidar com dicionários é contar a ocorrência de determinados itens. O tipo defaultdict pode simplificar essa tarefa, fornecendo uma contagem automática para itens ausentes.

from collections import defaultdict
fruits = ["apple", "banana", "banana", "apple", "orange", "apple"]
fruit_counter = defaultdict(int)
for fruit in fruits:
fruit_counter[fruit] += 1
print(fruit_counter)

Nesse exemplo, a função default_factory é int, que define um padrão de valor como 0. Ao acessar uma chave ausente, será retornada automaticamente a contagem zero. No loop, ao encontrar um item de fruta, a contagem da fruta correspondente é incrementada em 1. Ao final do loop, o defaultdict conterá a contagem de ocorrências de cada fruta.

Acumulando valores

O tipo defaultdict também pode ser útil ao acumular valores em um dicionário.

from collections import defaultdict
transactions = [
{"customer_id": 1, "amount": 100},
{"customer_id": 2, "amount": 50},
{"customer_id": 1, "amount": 200},
{"customer_id": 3, "amount": 75},
{"customer_id": 2, "amount": 150}
]
customer_total = defaultdict(float)
for transaction in transactions:
customer_id = transaction["customer_id"]
amount = transaction["amount"]
customer_total[customer_id] += amount
print(customer_total)

Nesse exemplo, a função default_factory é float, que define um padrão de valor como 0.0. No loop, a cada transação, o valor do montante é acumulado na chave correspondente ao ID do cliente no defaultdict. Dessa forma, ao final do loop, o defaultdict conterá o total acumulado de cada cliente.

Explorando mais sobre defaultdict

Além das operações básicas discutidas até agora, vamos explorar mais algumas funcionalidades do defaultdict.

defaultdict vs dict

Uma das principais diferenças entre um defaultdict e um dicionário comum é que o defaultdict não lança um KeyError ao tentar acessar uma chave ausente. Em vez disso, ele cria a chave ausente e associa automaticamente um valor padrão a ela usando a função default_factory. Por outro lado, um dicionário normal lançará um KeyError ao tentar acessar uma chave que não existe.

defaultdict.default_factory

Você pode acessar e modificar a função default_factory de um defaultdict por meio do atributo default_factory. Isso permite que você altere o comportamento padrão do defaultdict a qualquer momento, alterando a função default_factory para uma nova função ou classe.

from collections import defaultdict
my_dict = defaultdict(int)
print(my_dict.default_factory) # Output: <class 'int'>
my_dict.default_factory = float
print(my_dict.default_factory) # Output: <class 'float'>

Nesse exemplo, my_dict.default_factory é inicialmente int, que define um padrão de valor como 0. Em seguida, modificamos my_dict.default_factory para float, alterando assim o padrão de valor para 0.0. Isso significa que, se uma chave ausente for acessada no defaultdict após essa alteração, ela gerará um valor padrão de 0.0.

defaultdict vs dict.setdefault()

Uma alternativa para usar um defaultdict é usar o método setdefault() de um dicionário comum. A principal diferença é que o defaultdict oferece uma semântica mais elegante e eficiente. Vamos considerar o seguinte exemplo:

from collections import defaultdict
my_dict = defaultdict(int)
value1 = my_dict.setdefault("key1", 0)
value2 = my_dict.setdefault("key2", 0)

Nesse exemplo, o defaultdict criará automaticamente as chaves “key1” e “key2” com os valores padrão 0. No entanto, ao usar o método setdefault() de um dicionário comum, você precisa especificar um valor padrão explicitamente para cada chave, mesmo que a chave já exista.

my_dict = {}
value1 = my_dict.setdefault("key1", 0)
value2 = my_dict.setdefault("key2", 0)

É possível obter o mesmo resultado com um dicionário comum, mas a sintaxe é menos intuitiva e pode ser mais suscetível a erros.

defaultdict.__missing__()

O método especial __missing__() é chamado sempre que uma chave ausente é acessada em um defaultdict. Por padrão, esse método simplesmente retorna o valor padrão associado à chave pelo default_factory. No entanto, esse método pode ser substituído para fornecer comportamentos personalizados ao acessar chaves ausentes.

from collections import defaultdict
class MissingKeyDict(defaultdict):
def __missing__(self, key):
return f"Key '{key}' is missing"
my_dict = MissingKeyDict(str)
value = my_dict["key"]
print(value) # Output: "Key 'key' is missing"

Nesse exemplo, definimos uma classe derivada de defaultdict chamada MissingKeyDict que substitui o método __missing__(). Quando uma chave ausente é acessada, o método __missing__() é chamado e retorna uma mensagem personalizada indicando que a chave está ausente.

Emulando o tipo defaultdict do Python

Embora o tipo defaultdict do Python seja útil para muitas situações, pode haver casos em que você não pode usar o defaultdict diretamente. No entanto, você ainda pode emular o comportamento do defaultdict criando suas próprias classes ou funções personalizadas.

def default_value():
return 0
def my_dict():
return defaultdict(default_value)

Nesse exemplo, definimos uma função default_value() que retorna o valor padrão desejado e, em seguida, usamos essa função para criar um defaultdict personalizado por meio da função my_dict(). Dessa forma, podemos obter o mesmo comportamento de um defaultdict usando nossa própria implementação.

Passando argumentos para .default_factory

Além de usar funções simples para criar defaultdicts, você também pode usar funções mais complexas e até mesmo funções que aceitam argumentos. Existem várias maneiras de passar argumentos para a função default_factory, como usando lambda ou o módulo functools.partial().

Usando lambda

O lambda é uma função anônima que pode ser usada para criar funções simples em uma única linha. Você pode usar lambda para criar uma função sob demanda ao criar um defaultdict.

from collections import defaultdict
my_dict = defaultdict(lambda: "Unknown")
value = my_dict["key"]
print(value) # Output: "Unknown"

Nesse exemplo, usamos lambda: "Unknown" como a função default_factory. Isso cria uma função anônima que sempre retorna a string "Unknown" como valor padrão. Portanto, ao acessar uma chave ausente no defaultdict, o valor padrão será "Unknown".

Usando functools.partial()

O módulo functools fornece a função partial() que pode ser usada para criar funções personalizadas com argumentos fixos. Dessa forma, você pode criar uma função parcial que será usada como a função default_factory no defaultdict.

from collections import defaultdict
from functools import partial
def default_value(default):
return default
my_dict = defaultdict(partial(default_value, default="Unknown"))
value = my_dict["key"]
print(value) # Output: "Unknown"

Nesse exemplo, partial(default_value, default="Unknown") cria uma função parcial que usa a função default_value com o argumento fixo default="Unknown". Portanto, ao acessar uma chave ausente no defaultdict, o valor padrão será "Unknown".

Conclusão

O tipo defaultdict do Python é uma ferramenta poderosa para lidar com chaves ausentes em dicionários. Com ele, você pode simplificar tarefas comuns, como agrupamento, contagem e acumulação de valores. Além disso, você pode personalizar o comportamento do defaultdict para se adequar às suas necessidades específicas.

No entanto, tenha em mente que o defaultdict pode consumir mais memória do que um dicionário comum, pois armazena explicitamente todas as chaves ausentes e seus valores padrão. Portanto, use-o com sabedoria e considere outras abordagens, dependendo do contexto e dos requisitos do seu projeto.

Com o conhecimento adquirido neste tutorial, você estará pronto para usar o tipo defaultdict do Python para lidar com chaves ausentes em dicionários e melhorar suas habilidades de programação em Python.