Filtrer dict pour ne contenir que certaines touches?


497

J'ai un dictqui a tout un tas d'entrées. Je ne suis intéressé que par quelques-uns d'entre eux. Existe-t-il un moyen facile de tailler tous les autres?


Il est utile de dire quel type de clés (entiers? Chaînes? Dates? Objets arbitraires?) Et donc s'il existe un test simple (chaîne, expression régulière, appartenance à une liste ou inégalité numérique) pour vérifier quelles clés sont entrées ou sorties. Ou bien devons-nous appeler une ou des fonctions arbitraires pour le déterminer.
smci

@smci String keys. Ne pensez pas qu'il m'est même venu à l'esprit que je pouvais utiliser autre chose; J'ai
codé

Réponses:


656

Construire un nouveau dict:

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

Utilise la compréhension du dictionnaire.

Si vous utilisez une version qui en manque (c'est-à-dire Python 2.6 et versions antérieures), faites-la dict((your_key, old_dict[your_key]) for ...). C'est la même chose, mais plus laid.

Notez que cela, contrairement à la version de jnnnnn, a des performances stables (ne dépend que du nombre de your_keys) pour les old_dicts de n'importe quelle taille. Tant en termes de vitesse que de mémoire. Comme il s'agit d'une expression de générateur, elle traite un élément à la fois et ne passe pas en revue tous les éléments de old_dict.

Suppression de tout en place:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

8
"Utilise la compréhension du dictionnaire, si vous utilisez une version qui leur manque" == version <= 2.6
getekha

8
Lève une erreur KeyError si l'une des clés de filer n'est pas présente dans old_dict. Je suggérerais {k: d [k] pour k dans le filtre si k dans d}
Peter Gibson

1
@PeterGibson Oui, si cela fait partie des exigences, vous devez faire quelque chose . Que ce soit la suppression silencieuse des clés, l'ajout d'une valeur par défaut ou autre chose, cela dépend de ce que vous faites; il existe de nombreux cas d'utilisation où votre approche est erronée. Il y en a aussi beaucoup où une clé manquante old_dictindique un bogue ailleurs, et dans ce cas, je préfère de loin une erreur à des résultats silencieusement erronés.

@delnan, l'ajout "if k in d" vous ralentit si d est grand, je pensais juste que cela valait la peine d'être mentionné
Peter Gibson

7
@PeterGibson Ce n'est pas le cas, la recherche dans le dictionnaire est O (1).

130

Compréhension du dict un peu plus élégante:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

A voté. Je pensais ajouter une réponse similaire à celle-ci. Mais par simple curiosité, pourquoi {k: v pour k, v dans dict.items () ...} plutôt que {k: dict [k] pour k dans dict ...} Y a-t-il une différence de performances?
Hart Simha

4
Répondu à ma propre question. Le {k: dict [k] pour k dans dict ...} est environ 20-25% plus rapide, au moins en Python 2.7.6, avec un dictionnaire de 26 éléments (timeit (..., setup = "d = {chr (x + 97): x + 1 pour x dans la plage (26)} ")), en fonction du nombre d'éléments filtrés (le filtrage des clés de consonnes est plus rapide que le filtrage des clés de voyelle car vous recherchez moins d'articles). La différence de performances peut très bien devenir moins importante à mesure que la taille de votre dictionnaire augmente.
Hart Simha

5
Serait probablement le même perf si vous l'utilisiez à la mydict.iteritems()place. .items()crée une autre liste.
Pat

64

Voici un exemple en python 2.6:

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

La partie filtrage est l' ifinstruction.

Cette méthode est plus lente que la réponse de Delnan si vous ne souhaitez sélectionner que quelques-unes des très nombreuses clés.


11
sauf que j'utiliserais probablement if key in ('x','y','z')je suppose.
mpen

si vous savez déjà quelles clés vous voulez, utilisez la réponse de delnan. Si vous devez tester chaque clé avec une instruction if, utilisez la réponse de ransford.
jnnnnn

1
Cette solution présente un avantage supplémentaire. Si le dictionnaire est renvoyé à partir d'un appel de fonction coûteux (c'est-à-dire qu'un / old_dict est un appel de fonction), cette solution n'appelle la fonction qu'une seule fois. Dans un environnement impératif stockant le dictionnaire renvoyé par la fonction dans une variable n'est pas un gros problème, mais dans un environnement fonctionnel (par exemple dans un lambda) c'est une observation clé.
gae123

21

Vous pouvez le faire avec la fonction de projet de ma bibliothèque funcy :

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

Jetez également un œil à select_keys .


20

Code 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

Code 2:

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

Code 3:

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

Toutes les performances de code assemblées sont mesurées avec timeit en utilisant nombre = 1000, et collectées 1000 fois pour chaque morceau de code.

entrez la description de l'image ici

Pour python 3.6, les performances de trois façons de filtrer les touches dict sont presque les mêmes. Pour python 2.7, le code 3 est légèrement plus rapide.


juste curieux, avez-vous fait cette intrigue à partir de Python?
user5359531

1
ggplot2 in R - part of tidyverse
keithpjolley

18

Ce liner lambda devrait fonctionner:

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

Voici un exemple:

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}

Il s'agit d'une compréhension de base de la liste itérant sur vos clés dict (i en x) et génère une liste de paires de tuple (clé, valeur) si la clé réside dans la liste de clés souhaitée (y). Un dict () encapsule le tout pour produire un objet dict.


Devrait utiliser un setpour wanted_keys, mais sinon, il semble bon.
mpen

Cela me donne un dictionnaire vide si mon dictionnaire d'origine contient des listes à la place des valeurs. Des solutions?
FaCoffee

@Francesco, pouvez-vous donner un exemple? Si je lance dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, il revient {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}comme prévu.
Jim

J'ai essayé cela avec: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}et le résultat a été {}, que j'ai supposé être un dict blanc.
FaCoffee

Une chose, "dict" est un mot réservé, vous ne devez donc pas l'utiliser pour nommer un dict. Quelles étaient les clés que vous tentiez de retirer? Si je lance foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2')):, j'obtiens: {'0': [1, 3], '2': [1, 4]}quel est le résultat escompté
Jim

14

Compte tenu de votre dictionnaire d'origine origet de l'ensemble des entrées qui vous intéressent keys:

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

ce qui n'est pas aussi agréable que la réponse de delnan, mais devrait fonctionner dans toutes les versions de Python d'intérêt. Il est cependant fragile pour chaque élément keysexistant dans votre dictionnaire d'origine.


Eh bien, c'est fondamentalement une version enthousiaste de la "version générateur de tuple" de ma compréhension de dict. Très compatible en effet, bien que les expressions génératrices aient été introduites dans la version 2.4, printemps 2005 - sérieusement, est-ce que quelqu'un l'utilise encore?

1
Je ne suis pas en désaccord; 2.3 ne devrait vraiment plus exister. Cependant, comme une étude obsolète de l'utilisation de 2.3: moinmo.in/PollAboutRequiringPython24 Version courte: RHEL4, SLES9, livrée avec OS X 10.4
Kai

7

Sur la base de la réponse acceptée par delnan.

Que faire si l'une de vos clés recherchées n'est pas dans l'ancien_dict? La solution delnan lèvera une exception KeyError que vous pouvez intercepter. Si ce n'est pas ce dont vous avez besoin, vous voudrez peut-être:

  1. n'inclut que les clés qui s'exécutent à la fois dans l'ancien_dict et dans votre jeu de clés recherchées.

    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. ont une valeur par défaut pour les clés qui n'est pas définie dans 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'}

Vous pourriez aussi faire{k: old_dict.get(k, default) for k in ...}
Moberg

6

Cette fonction fera l'affaire:

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}

Tout comme la version de delnan, celle-ci utilise la compréhension du dictionnaire et a des performances stables pour les grands dictionnaires (dépendant uniquement du nombre de clés que vous autorisez, et non du nombre total de clés dans le dictionnaire).

Et tout comme la version de MyGGan, celle-ci permet à votre liste de clés d'inclure des clés qui peuvent ne pas exister dans le dictionnaire.

Et en bonus, voici l'inverse, où vous pouvez créer un dictionnaire en excluant certaines clés dans l'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}

Notez que contrairement à la version de delnan, l'opération n'est pas effectuée sur place, donc les performances sont liées au nombre de clés dans le dictionnaire. Cependant, l'avantage de ceci est que la fonction ne modifiera pas le dictionnaire fourni.

Modifier: Ajout d'une fonction distincte pour exclure certaines touches d'un dict.


Vous devez permettre keysà tout type d'itérable, comme ce que l' ensemble accepte.
mpen

Ah, bon appel, merci de l'avoir signalé. Je ferai cette mise à jour.
Ryan

Je me demande si vous êtes mieux avec deux fonctions. Si vous demandiez à 10 personnes "cela invertsignifie que l' keysargument est conservé ou que l' keysargument est rejeté?", Combien d'entre elles seraient d'accord?
skatenerd

Mise à jour. Laissez-moi savoir ce que vous pensez.
Ryan

Cela semble ne pas fonctionner si le dict d'entrée a des listes à la place des valeurs. Dans ce cas, vous obtenez un dict nul. Des solutions?
FaCoffee

4

Si nous voulons créer un nouveau dictionnaire avec les clés sélectionnées supprimées, nous pouvons utiliser la compréhension du dictionnaire
Par exemple:

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}

Soigné. Fonctionne uniquement dans Python 3. Python 2 dit "TypeError: type (s) d'opérande non pris en charge pour -: 'list' et 'set'"
mpen

Ajout de set (d.keys ()) pour Python 2. Cela fonctionne lorsque je cours.
Srivastava

2

Une autre option:

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

Mais vous obtenez un list(Python 2) ou un itérateur (Python 3) retourné par filter(), pas un dict.


Enveloppez filtereddans dictet vous revenez le dictionnaire!
CMCDragonkai

1

Forme courte:

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

Comme la plupart des réponses le suggèrent afin de maintenir la concision, nous devons créer un objet en double, que ce soit un listou dict. Celui-ci crée un jetable listmais supprime les clés dans l'original dict.


0

Voici une autre méthode simple à utiliser deldans une doublure:

for key in e_keys: del your_dict[key]

e_keysest la liste des clés à exclure. Il mettra à jour votre dict plutôt que de vous en donner un nouveau.

Si vous voulez un nouveau dict de sortie, faites une copie du dict avant de le supprimer:

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

for key in e_keys: del new_dict[key]

0

Vous pourriez utiliser python-benedict, c'est une sous-classe dict.

Installation: pip install python-benedict

from benedict import benedict

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

C'est open-source sur GitHub: https://github.com/fabiocaccamo/python-benedict


Avertissement: je suis l'auteur de cette bibliothèque.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.