Recentemente observei que um problema recorrente em python, principalmente para iniciantes na linguagem, é mapear valores de uma lista para outros valores específicos. Um exemplo deste tipo de problema é: dado uma lista de objetos, separá-los de acordo com seu tipo.
Para resolver este problema podemos criar um dicionário onde as chaves são os tipos e os valores são listas de objetos daquele tipo. Uma solução é uma função de tenha o seguinte comportamento:
>>> l = [1, 2.6, 5.3, 'asdasd', 8, 'bola', None]
>>> func(l)
{'int': [1, 8], 'float': [2.6, 5.3], 'str': ['asdasd', 'bola'], 'NoneType': [None]}
Uma forma de construir este dicionário é descobrindo todas as possíveis chaves e
populando-as com uma lista vazia antes de chamar append
:
def func(l):
d = {}
keys = {type(i).__name__ for i in l}
for k in keys:
d[k] = []
for v in l:
key = type(v).__name__
d[key].append(v)
return d
# >>> l = [1, 2.6, 5.3, 'asdasd', 8, 'bola', None]
# >>> func(l)
# {'int': [1, 8], 'str': ['asdasd', 'bola'], 'NoneType': [None], 'float': [2.6, 5.3]}
Mas isto é simplesmente cansativo.
Inicializar dicionários é uma tarefa comum então o método
setdefault
foi adicionado à classe dict
para dar um valor padrão à uma chave caso ela
ainda não esteja inserida:
>>> d = {}
>>> d.setdefault('a', []).append('valor1')
>>> d.setdefault('a', []).append('valor2')
>>> d.setdefault('b', []).append('valor3')
>>> d.setdefault('b', []).append('valor4')
>>> d
{'b': ['valor3', 'valor4'], 'a': ['valor1', 'valor2']}
Podemos usar este método para reescrever a função:
def func(l):
d = {}
for v in l:
key = type(v).__name__
d.setdefault(key, []).append(v)
return d
# >>> l = [1, 2.6, 5.3, 'asdasd', 8, 'bola', None]
# >>> func(l)
# {'float': [2.6, 5.3], 'str': ['asdasd', 'bola'], 'int': [1, 8], 'NoneType': [None]}
Bem melhor!
O problema aqui é que teríamos que lembrar de chamar setdefault
toda vez. Para
resolver este tipo de problema a biblioteca padrão oferece
collections.defaultdict
:
>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> d['a'].append('value1')
>>> d['a'].append('value2')
>>> d['b'].append('value3')
>>> d['b'].append('value4')
>>> d['a']
['value1', 'value2']
>>> d['b']
['value3', 'value4']
Usando collections.defaultdict
para reescrever a função ficamos com:
def func(l):
d = defaultdict(list)
for v in l:
k = type(v).__name__
d[k].append(v)
return dict(d)
# >>> l = [1, 2.6, 5.3, 'asdasd', 8, 'bola', None]
# >>> func(l)
# {'float': [2.6, 5.3], 'str': ['asdasd', 'bola'], 'int': [1, 8], 'NoneType': [None]}
Note que passamos uma função para o construtor do defaultdict
. Está função é
chamada para construir o objeto padrão quando a chave ainda não tiver sido
inserida. Poderíamos usar outras estruturas built-in, da biblioteca padrão ou
mesmo funções próprias para construir o valor padrão.