Como funciona o collections.defaultdict?


532

Eu li os exemplos nos documentos python, mas ainda não consigo descobrir o que esse método significa. Alguém pode ajudar? Aqui estão dois exemplos dos documentos python

>>> from collections import defaultdict

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]

e

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

os parâmetros inte listsão para quê?


15
BTW, dependendo do seu caso de uso, não se esqueça de congelar o decreto-padrão para uso somente leitura, definindo-o default_factory = Nonedepois de concluir o preenchimento do decreto-padrão. Veja esta pergunta .
Acumenus 30/10/16

Respostas:


598

Normalmente, um dicionário Python lança a KeyErrorse você tentar obter um item com uma chave que não está atualmente no dicionário. Por defaultdictoutro lado, simplesmente criará quaisquer itens que você tentar acessar (desde que eles ainda não existam). Para criar um item "padrão", ele chama o objeto de função que você passa para o construtor (mais precisamente, é um objeto "solicitável" arbitrário, que inclui objetos de função e tipo). Para o primeiro exemplo, os itens padrão são criados usando int(), que retornará o objeto inteiro 0. Para o segundo exemplo, os itens padrão são criados usando list(), que retorna um novo objeto de lista vazio.


4
É funcionalmente diferente de usar d.get (key, default_val)?
Ambareesh

29
O @Ambareesh d.get(key, default)nunca modificará seu dicionário - ele retornará o padrão e deixará o dicionário inalterado. defaultdict, por outro lado, inserirá uma chave no dicionário, se ainda não estiver lá. Isso é uma grande diferença; veja os exemplos na pergunta para entender o porquê.
Sven Marnach

Como sabemos qual é o valor padrão para cada tipo? 0 para int () e [] para list () são intuitivos, mas também pode haver tipos mais complexos ou auto-definidos.
Sean

1
O @Sean defaultdictchama qualquer construtor que você fornecer. Se você digitar um tipo T, os valores serão construídos usando T(). Nem todos os tipos podem ser construídos sem passar nenhum parâmetro. Se você deseja construir esse tipo, precisa de uma função de wrapper ou algo parecido functools.partial(T, arg1, arg2).
Sven Marnach 11/03

224

defaultdictsignifica que, se uma chave não for encontrada no dicionário, em vez de KeyErrorser lançada, uma nova entrada será criada. O tipo desta nova entrada é fornecido pelo argumento de defaultdict.

Por exemplo:

somedict = {}
print(somedict[3]) # KeyError

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0

10
"O tipo deste novo par é dado pelo argumento de defaultdict." Observe que o argumento pode ser qualquer objeto que possa ser chamado - não apenas digitar funções. Por exemplo, se foo fosse uma função que retornasse "bar", foo poderia ser usado como argumento para o ditado padrão e se uma chave não presente fosse acessada, seu valor seria definido como "bar".
Lf215 29/07/2013

13
Ou se você só quer voltar "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert

A quarta linha retornou 0o número inteiro, se fosse someddict = defaultdict(list)ele retorna [ ]. 0 é o número inteiro padrão? Ou [] a lista padrão?
Gathide 5/17/17

Nem. 0é imutável - no CPython todos os valores de -5para 256são singletons em cache, mas esse é um comportamento específico da implementação - em ambos os casos, uma nova instância é "criada" sempre que com int()ou list(). Dessa forma, d[k].append(v)pode funcionar sem preencher o dicionário com referências à mesma lista, o que tornaria defaultdictquase inútil. Se esse fosse o comportamento, defaultdictlevaria um valor, não um lambda, como parâmetro. (Desculpem a terrível explicação!)
wizzwizz4

93

defaultdict

"O dicionário padrão inclui o método setdefault () para recuperar um valor e estabelecer um padrão se o valor não existir. Por outro lado, defaultdictpermite que o chamador especifique o padrão (valor a ser retornado) antecipadamente quando o contêiner é inicializado."

conforme definido por Doug Hellmann na The Python Standard Library por exemplo

Como usar o defaultdict

Importar padrão

>>> from collections import defaultdict

Inicializar defaultdict

Inicialize passando

exigível como seu primeiro argumento (obrigatório)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

** kwargs como seu segundo argumento (opcional)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

ou

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

Como isso funciona

Como é uma classe filho do dicionário padrão, ele pode executar as mesmas funções.

Mas, no caso de passar uma chave desconhecida, ela retorna o valor padrão em vez de erro. Por exemplo:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

Caso você queira alterar o valor padrão, substitua default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

ou

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

Exemplos na pergunta

Exemplo 1

Como int foi passado como default_factory, qualquer chave desconhecida retornará 0 por padrão.

Agora, como a cadeia é passada no loop, aumentará a contagem desses alfabetos em d.

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> d.default_factory
<type 'int'>
>>> for k in s:
...     d[k] += 1
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
>>> d
defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'm': 1})

Exemplo 2

Como uma lista foi passada como default_factory, qualquer chave desconhecida (inexistente) retornará [] (por exemplo, lista) por padrão.

Agora, como a lista de tuplas é passada no loop, ele acrescentará o valor em d [color]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> d.default_factory
<type 'list'>
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
>>> d
defaultdict(<type 'list'>, {'blue': [2, 4], 'red': [1], 'yellow': [1, 3]})

20

Os dicionários são uma maneira conveniente de armazenar dados para recuperação posterior por nome (chave). As chaves devem ser objetos únicos e imutáveis ​​e geralmente são cadeias de caracteres. Os valores em um dicionário podem ser qualquer coisa. Para muitas aplicações, os valores são tipos simples, como números inteiros e seqüências de caracteres.

Torna-se mais interessante quando os valores em um dicionário são coleções (listas, dictos etc.) Nesse caso, o valor (uma lista ou dict vazio) deve ser inicializado na primeira vez que uma determinada chave é usada. Embora isso seja relativamente fácil de fazer manualmente, o tipo de comando padrão automatiza e simplifica esses tipos de operações. Um comando padrão funciona exatamente como um comando normal, mas é inicializado com uma função (“fábrica padrão”) que não aceita argumentos e fornece o valor padrão para uma chave inexistente.

Um padrão nunca irá gerar um KeyError. Qualquer chave que não existe obtém o valor retornado pela fábrica padrão.

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>>Chunky Monkey

print(ice_cream['Joe'])
>>>Vanilla

Aqui está outro exemplo de como usar o defaultdict, podemos reduzir a complexidade

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result


x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

Concluindo, sempre que você precisar de um dicionário e o valor de cada elemento começar com um valor padrão, use um padrão.


18

Há uma ótima explicação para os defaultdicts aqui: http://ludovf.net/blog/python-collections-defaultdict/

Basicamente, os parâmetros int e list são funções que você passa. Lembre-se de que o Python aceita nomes de funções como argumentos. int retorna 0 por padrão e list retorna uma lista vazia quando chamada entre parênteses.

Nos dicionários normais, se no seu exemplo eu tentar chamar d[a], receberei um erro (KeyError), pois apenas existem as chaves m, s, ie ep e a chave a não foi inicializada. Mas em um padrão, ele assume o nome de uma função como argumento, quando você tenta usar uma chave que não foi inicializada, simplesmente chama a função que você passou e atribui seu valor de retorno como o valor da nova chave.


7

Como a pergunta é sobre "como funciona", alguns leitores podem querer ver mais porcas e parafusos. Especificamente, o método em questão é o__missing__(key) método. Consulte: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Mais concretamente, esta resposta mostra como fazer uso de __missing__(key)uma maneira prática: https://stackoverflow.com/a/17956989/1593924

Para esclarecer o que significa 'chamar', aqui está uma sessão interativa (da 2.7.6, mas também deve funcionar na v3):

>>> x = int
>>> x
<type 'int'>
>>> y = int(5)
>>> y
5
>>> z = x(5)
>>> z
5

>>> from collections import defaultdict
>>> dd = defaultdict(int)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd = defaultdict(x)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd['a']
0
>>> dd
defaultdict(<type 'int'>, {'a': 0})

Esse foi o uso mais típico do defaultdict (exceto o uso inútil da variável x). Você pode fazer o mesmo com 0 como o valor padrão explícito, mas não com um valor simples:

>>> dd2 = defaultdict(0)

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    dd2 = defaultdict(0)
TypeError: first argument must be callable

Em vez disso, o seguinte funciona porque passa em uma função simples (cria instantaneamente uma função sem nome que não aceita argumentos e sempre retorna 0):

>>> dd2 = defaultdict(lambda: 0)
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {})
>>> dd2['a']
0
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})
>>> 

E com um valor padrão diferente:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
>>> 

7

Meus 2 ¢: você também pode subclass defaultdict:

class MyDict(defaultdict):
    def __missing__(self, key):
        value = [None, None]
        self[key] = value
        return value

Isso pode ser útil para casos muito complexos.


4

O comportamento de defaultdictpode ser facilmente imitado usando em dict.setdefaultvez ded[key] em todas as chamadas.

Em outras palavras, o código:

from collections import defaultdict

d = defaultdict(list)

print(d['key'])                        # empty list []
d['key'].append(1)                     # adding constant 1 to the list
print(d['key'])                        # list containing the constant [1]

é equivalente a:

d = dict()

print(d.setdefault('key', list()))     # empty list []
d.setdefault('key', list()).append(1)  # adding constant 1 to the list
print(d.setdefault('key', list()))     # list containing the constant [1]

A única diferença é que, usando defaultdict, o construtor da lista é chamado apenas uma vez e o uso dict.setdefaultdo construtor da lista é chamado com mais frequência (mas o código pode ser reescrito para evitar isso, se realmente necessário).

Alguns podem argumentar que há uma consideração de desempenho, mas esse tópico é um campo minado. Esta postagem mostra que não há um grande ganho de desempenho no uso do defaultdict, por exemplo.

IMO, defaultdict é uma coleção que adiciona mais confusão do que benefícios ao código. Inútil para mim, mas outros podem pensar diferente.


3

A ferramenta defaultdict é um contêiner na classe de coleções do Python. É semelhante ao contêiner usual do dicionário (dict), mas tem uma diferença: o tipo de dados dos campos de valor é especificado na inicialização.

Por exemplo:

from collections import defaultdict

d = defaultdict(list)

d['python'].append("awesome")

d['something-else'].append("not relevant")

d['python'].append("language")

for i in d.items():

    print i

Isso imprime:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])

"O tipo de dados dos campos de valor é especificado na inicialização": isso não está correto. Uma função de fábrica de elemento é fornecida. Aqui listestá a função a ser chamada para preencher um valor ausente, não o tipo dos objetos a serem criados. Por exemplo, para ter um valor padrão de 1, você usaria o lambda:1que obviamente não é um tipo.
Asac

2

Eu acho que é melhor usado no lugar de uma declaração de caso de switch. Imagine se tivermos uma declaração de caso de mudança como abaixo:

option = 1

switch(option) {
    case 1: print '1st option'
    case 2: print '2nd option'
    case 3: print '3rd option'
    default: return 'No such option'
}

Não há switchinstruções de caso disponíveis em python. Podemos conseguir o mesmo usando defaultdict.

from collections import defaultdict

def default_value(): return "Default Value"
dd = defaultdict(default_value)

dd[1] = '1st option'
dd[2] = '2nd option'
dd[3] = '3rd option'

print(dd[4])    
print(dd[5])    
print(dd[3])

Imprime:

Default Value
Default Value
3rd option

No trecho acima dd, não há chaves 4 ou 5 e, portanto, imprime um valor padrão que configuramos em uma função auxiliar. Isso é muito melhor do que um dicionário bruto, onde a KeyErroré lançada se a chave não estiver presente. A partir disso, é evidente que defaultdictmais se parece com uma declaração de caso de mudança em que podemos evitar if-elif-elif-elseblocos complicados .

Mais um bom exemplo que me impressionou muito neste site é:

>>> from collections import defaultdict
>>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
>>> food_count = defaultdict(int) # default value of int is 0
>>> for food in food_list:
...     food_count[food] += 1 # increment element's value by 1
...
defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
>>>

Se tentarmos acessar outros itens que não sejam eggse spamobteremos uma contagem de 0.


2

Sem defaultdict, você provavelmente pode atribuir novos valores a chaves invisíveis, mas não pode modificá-lo. Por exemplo:

import collections
d = collections.defaultdict(int)
for i in range(10):
  d[i] += i
print(d)
# Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})

import collections
d = {}
for i in range(10):
  d[i] += i
print(d)
# Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0

2

Bem, o defaultdict também pode gerar keyerror no seguinte caso:

    from collections import defaultdict
    d = defaultdict()
    print(d[3]) #raises keyerror

Lembre-se sempre de fornecer argumentos para o defaultdict como defaultdict (int).


0

O dicionário padrão inclui o método setdefault () para recuperar um valor e estabelecer um padrão se o valor não existir. Por outro lado, defaultdict permite que o chamador especifique o padrão antecipadamente quando o contêiner é inicializado.

import collections

def default_factory():
    return 'default value'

d = collections.defaultdict(default_factory, foo='bar')
print 'd:', d
print 'foo =>', d['foo']
print 'bar =>', d['bar']

Isso funciona bem desde que seja apropriado que todas as chaves tenham o mesmo padrão. Pode ser especialmente útil se o padrão for um tipo usado para agregar ou acumular valores, como uma lista, conjunto ou mesmo int. A documentação da biblioteca padrão inclui vários exemplos de uso do defaultdict dessa maneira.

$ python collections_defaultdict.py

d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
foo => bar
bar => default value

0

Em resumo:

defaultdict(int) - o argumento int indica que os valores serão do tipo int.

defaultdict(list) - a lista de argumentos indica que os valores serão do tipo lista.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.