Dict de filtro para conter apenas determinadas chaves?

496

Eu tenho um dictque tem um monte de entradas. Estou interessado apenas em alguns deles selecionados. Existe uma maneira fácil de remover todos os outros?

mpen
fonte
É útil dizer que tipo de chaves (números inteiros? Strings? Datas? Objetos arbitrários?) E, portanto, se existe um teste simples (string, regex, associação à lista ou desigualdade numérica) para verificar quais chaves estão dentro ou fora. Ou então precisamos chamar uma função arbitrária para determinar isso.
SMCI
@smci String keys. Não pense que me ocorreu que eu poderia usar qualquer outra coisa; Estive codificação em JS e PHP por tanto tempo ...
MPEN

Respostas:

656

Construindo um novo ditado:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Usa compreensão de dicionário.

Se você usa uma versão que não possui (por exemplo, Python 2.6 e anterior), faça-o dict((your_key, old_dict[your_key]) for ...). É o mesmo, embora mais feio.

Observe que isso, diferente da versão do jnnnnn, tem desempenho estável (depende apenas do número de suas_chaves) para old_dicts de qualquer tamanho. Tanto em termos de velocidade e memória. Como essa é uma expressão geradora, ele processa um item de cada vez e não analisa todos os itens do old_dict.

Removendo tudo no local:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]
Comunidade
fonte
8
"Usa compreensão dicionário, se você usar uma versão que falta-lhes" == versão <= 2,6
getekha
8
Lança um KeyError se uma das chaves do arquivador não estiver presente no old_dict. Eu sugeriria {k: d [k] para k no filtro, se k em d}
Peter Gibson
1
@ PeterGibson Sim, se isso faz parte dos requisitos, você precisa fazer algo a respeito. Seja descartar silenciosamente as chaves, adicionar um valor padrão ou qualquer outra coisa, depende do que você está fazendo; existem muitos casos de uso em que sua abordagem está errada. Também existem muitos em que uma chave que falta old_dictindica um erro em outro lugar e, nesse caso, eu prefiro um erro a resultados silenciosamente errados.
@delnan, também o complemento "se k em d" retarda para baixo, se d é grande, eu apenas pensei que era vale a pena mencionar
Peter Gibson
7
@ PeterGibson Não, a pesquisa de dicionário é O (1).
130

Compreensão de ditado um pouco mais elegante:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}
Ransford
fonte
Votado. Eu estava pensando em adicionar uma resposta semelhante a esta. Apenas por curiosidade, por que {k: v para k, v em dict.items () ...} em vez de {k: dict [k] para k em dict ...} Existe alguma diferença de desempenho?
Hart Simha
4
Respondeu minha própria pergunta. O {k: dict [k] para k in dict ...} é cerca de 20 a 25% mais rápido, pelo menos no Python 2.7.6, com um dicionário de 26 itens (timeit (..., setup = "d = {chr (x + 97): x + 1 para x no intervalo (26)} ")), dependendo de quantos itens estão sendo filtrados (filtrar as chaves consoantes é mais rápido do que filtrar as chaves de vogal, porque você está olhando para cima menos itens). A diferença no desempenho pode muito bem se tornar menos significativa à medida que o tamanho do seu dicionário aumenta.
Hart Simha
5
Provavelmente seria o mesmo perf se você usasse mydict.iteritems(). .items()cria outra lista.
Pat
64

Aqui está um exemplo no python 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

A parte de filtragem é a ifdeclaração.

Esse método é mais lento que a resposta de delnan, se você deseja selecionar apenas algumas das muitas teclas.

jnnnnn
fonte
11
exceto que eu provavelmente usaria, if key in ('x','y','z')eu acho.
MPEN
Se você já sabe quais teclas deseja, use a resposta de delnan. Se você precisar testar cada chave com uma declaração if, use a resposta de ransford.
jnnnnn 19/09/2015
1
Esta solução tem mais uma vantagem. Se o dicionário for retornado de uma chamada de função cara (ou seja, um / old_dict é uma chamada de função), esta solução chama a função apenas uma vez. Em um ambiente imperativo para armazenar o dicionário retornado pela função em uma variável não é grande coisa, mas em um ambiente funcional (por exemplo, em um lambda), essa é uma observação importante.
precisa saber é
21

Você pode fazer isso com a função de projeto da minha biblioteca de funções :

from funcy import project
small_dict = project(big_dict, keys)

Veja também select_keys .

Suor
fonte
20

Código 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Código 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Código 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Todo o desempenho do código é medido com timeit usando número = 1000 e coletado 1000 vezes para cada código.

insira a descrição da imagem aqui

Para o python 3.6, o desempenho de três formas de chaves de filtro é quase o mesmo. Para o python 2.7, o código 3 é um pouco mais rápido.

AA
fonte
apenas curioso, você fez esse enredo do Python?
user5359531
1
ggplot2 em R - parte de tidyverse
keithpjolley
18

Este lambda de um liner deve funcionar:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Aqui está um exemplo:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

É uma compreensão básica da lista que itera sobre as chaves de ditado (i em x) e gera uma lista de pares de tupla (chave, valor) se a chave estiver na lista de chaves desejada (y). Um dict () envolve a coisa toda para produzir como um objeto dict.

Jim
fonte
Deve usar um setpara wanted_keys, mas parece bom.
MPEN
Isso me fornece um dicionário em branco se meu dicionário original contiver listas no lugar de valores. Alguma solução alternativa?
FaCoffee 27/10
@Francesco, você pode fornecer um exemplo? Se eu executar dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, ele retornará {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}como pretendido.
28415 Jim
Eu tentei isso com: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}e o resultado foi {}, que eu assumi ser um ditado em branco.
FaCoffee 28/10/2015
Uma coisa, "dict" é uma palavra reservada, portanto você não deve usá-la para nomear um dict. Quais foram as chaves que você estava tentando retirar? Se eu executar: foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2')), eu recebo: {'0': [1, 3], '2': [1, 4]}que é o resultado pretendido
Jim
14

Dado o seu dicionário original orige o conjunto de entradas em que você está interessado keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

o que não é tão bom quanto a resposta de delnan, mas deve funcionar em todas as versões de interesse do Python. É, no entanto, frágil para cada elemento keysexistente no seu dicionário original.

Kai
fonte
Bem, essa é basicamente uma versão ansiosa da "versão do gerador de tuplas" da minha compreensão de ditados. Muito compatível, de fato, embora as expressões geradoras tenham sido introduzidas na 2.4, primavera de 2005 - sério, alguém ainda está usando isso?
1
Eu não discordo; 2.3 realmente não deveria existir mais. No entanto, como uma pesquisa desatualizada do uso da versão 2.3: moinmo.in/PollAboutRequiringPython24 Versão curta: RHEL4, SLES9, enviado com o OS X 10.4
Kai
7

Com base na resposta aceita por delnan.

E se uma de suas chaves desejadas não estiver no old_dict? A solução delnan lançará uma exceção KeyError que você pode capturar. Se não é isso que você precisa, talvez você queira:

  1. inclua apenas chaves que existam tanto no ditado antigo quanto no seu conjunto de chaves desejadas.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. tem um valor padrão para chaves que não está definido no old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}
MyGGaN
fonte
Você também pode fazer{k: old_dict.get(k, default) for k in ...}
Moberg:
6

Esta função fará o truque:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Assim como a versão de delnan, esta usa compreensão de dicionário e tem desempenho estável para dicionários grandes (depende apenas do número de chaves que você permite, e não do número total de chaves no dicionário).

E, assim como a versão do MyGGan, esta permite que sua lista de chaves inclua chaves que podem não existir no dicionário.

E como bônus, aqui está o inverso, onde você pode criar um dicionário excluindo determinadas chaves no original:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Observe que, diferentemente da versão do delnan, a operação não é realizada no local, portanto o desempenho está relacionado ao número de chaves no dicionário. No entanto, a vantagem disso é que a função não modifica o dicionário fornecido.

Editar: adicionada uma função separada para excluir determinadas teclas de um ditado.

Ryan
fonte
Você deve permitir keyspor qualquer tipo de iterável, como o que o conjunto aceita.
MPEN
Ah, boa ligação, obrigado por apontar isso. Eu vou fazer essa atualização.
Ryan
Gostaria de saber se você está melhor com duas funções. Se você perguntasse a 10 pessoas " invertimplica que o keysargumento seja mantido ou que o keysargumento seja rejeitado?", Quantas delas concordariam?
Skatenerd
Atualizada. Diz-me o que pensas.
21915 Ryan
Parece que não está funcionando se o ditado de entrada tiver listas no lugar de valores. Nesse caso, você recebe um ditado nulo. Alguma solução alternativa?
FaCoffee 27/10
4

Se queremos criar um novo dicionário com as chaves selecionadas removidas, podemos fazer uso da compreensão do dicionário.
Por exemplo:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}
Srivastava
fonte
Arrumado. Funciona apenas no Python 3. O Python 2 diz "TypeError: tipo (s) de operando não suportado para -: 'list' e 'set'"
mpen
Adicionado set (d.keys ()) para o Python 2. Isso está funcionando quando executo.
Srivastava
2

Outra opção:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Mas você obtém um list(Python 2) ou um iterador (Python 3) retornado por filter(), não um dict.

Marsl
fonte
Envoltório filteredem dicte você recebe de volta o dicionário!
CMCDragonkai
1

Forma curta:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Como a maioria das respostas sugere, a fim de manter a concisão, precisamos criar um objeto duplicado, seja ele listou dict. Este cria um descarte, listmas exclui as chaves no original dict.

nehem
fonte
0

Aqui está outro método simples usando delem um forro:

for key in e_keys: del your_dict[key]

e_keysé a lista das chaves a serem excluídas. Ele atualizará seu ditado em vez de fornecer um novo.

Se você deseja um novo dict de saída, faça uma cópia do dict antes de excluir:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]
Trovão preto
fonte
0

Você poderia usar python-benedict, é uma subclasse dict.

Instalação: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

É de código aberto no GitHub: https://github.com/fabiocaccamo/python-benedict


Isenção de responsabilidade: sou o autor desta biblioteca.

Fabio Caccamo
fonte