Utilisation du type Python defaultdict pour gérer les clés manquantes – Real Python

By | mars 11, 2020

Expert Python

Un problème courant que vous pouvez rencontrer lorsque vous travaillez avec des dictionnaires Python est d'essayer d'accéder ou de modifier des clés qui n'existent pas dans le dictionnaire. Cela soulèvera un KeyError et briser l'exécution de votre code. Pour gérer ce genre de situations, la bibliothèque standard fournit le Python defaultdict type, une classe de type dictionnaire qui est disponible pour vous dans collections.

Le Python defaultdict se comporte presque exactement comme un dictionnaire Python standard, mais si vous essayez d'accéder ou de modifier une clé manquante, defaultdict crée automatiquement la clé et génère une valeur par défaut pour celle-ci. Cela fait defaultdict une option précieuse pour gérer les clés manquantes dans les dictionnaires.

Dans ce didacticiel, vous apprendrez:

  • Comment utiliser le Python defaultdict taper pour gestion des clés manquantes dans un dictionnaire
  • Quand et pourquoi utiliser un Python defaultdict plutôt qu'un habitué dicter
  • Comment utiliser un defaultdict pour regroupement, compte, et accumuler opérations

Avec ces connaissances à votre disposition, vous serez en meilleure condition pour utiliser efficacement le Python defaultdict tapez vos défis de programmation au jour le jour.

Pour tirer le meilleur parti de ce didacticiel, vous devez avoir une compréhension préalable de ce que sont les dictionnaires Python et comment les utiliser. Si vous avez besoin de vous rafraîchir, consultez les ressources suivantes:

Gestion des clés manquantes dans les dictionnaires

Un problème courant que vous pouvez rencontrer lorsque vous travaillez avec des dictionnaires Python est comment gérer les clés manquantes. Si votre code est fortement basé sur des dictionnaires, ou si vous créez des dictionnaires à la volée tout le temps, vous remarquerez bientôt que le traitement des KeyError les exceptions peuvent être assez ennuyeuses et peuvent ajouter une complexité supplémentaire à votre code. Avec les dictionnaires Python, vous avez au moins quatre façons disponibles de gérer les clés manquantes:

  1. Utilisation .définir par defaut()
  2. Utilisation .avoir()
  3. Utilisez le entrer le dict idiome
  4. Utiliser un essayer et sauf bloquer

Les documents Python expliquent .définir par defaut() et .avoir() comme suit:

setdefault (clé[, default])

Si clé est dans le dictionnaire, retourne sa valeur. Sinon, insérez clé avec une valeur de défaut et retour défaut. défaut par défaut à Aucun.

Obtenir la clé[, default])

Renvoie la valeur de clé si clé est dans le dictionnaire, sinon défaut. Si défaut n'est pas donné, il est par défaut Aucun, de sorte que cette méthode ne soulève jamais KeyError.

(La source)

Voici un exemple d'utilisation .définir par defaut() gérer les clés manquantes dans un dictionnaire:

>>>

>>> a_dict = 
>>> a_dict[[[['missing_key']
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    a_dict[[[['missing_key']
KeyError: 'missing_key'
>>> a_dict.définir par defaut('missing_key', 'valeur par défaut')
'valeur par défaut'
>>> a_dict[[[['missing_key']
'valeur par défaut'
>>> a_dict.définir par defaut('missing_key', «une autre valeur par défaut»)
'valeur par défaut'
>>> a_dict
'missing_key': 'valeur par défaut'

Dans le code ci-dessus, vous utilisez .définir par defaut() pour générer une valeur par défaut pour clé_ manquante. Notez que votre dictionnaire, a_dict, a maintenant une nouvelle clé appelée clé_ manquante dont la valeur est 'valeur par défaut'. Cette clé n'existait pas avant que vous ayez appelé .définir par defaut(). Enfin, si vous appelez .définir par defaut() sur une clé existante, l'appel n'aura aucun effet sur le dictionnaire. Votre clé contiendra la valeur d'origine au lieu de la nouvelle valeur par défaut.

En revanche, si vous utilisez .avoir(), vous pouvez alors coder quelque chose comme ceci:

>>>

>>> a_dict = 
>>> a_dict.avoir('missing_key', 'valeur par défaut')
'valeur par défaut'
>>> a_dict

Ici, vous utilisez .avoir() pour générer une valeur par défaut pour clé_ manquante, mais cette fois, votre dictionnaire reste vide. Ceci est dû au fait .avoir() renvoie la valeur par défaut, mais cette valeur n'est pas ajoutée au dictionnaire sous-jacent. Par exemple, si vous avez un dictionnaire appelé , alors vous pouvez supposer que .avoir() fonctionne quelque chose comme ceci:

D.get (clé, par défaut) -> D[key] si clé en D, sinon par défaut

Avec ce pseudo-code, vous pouvez comprendre comment .avoir() travaille en interne. Si la clé existe, alors .avoir() renvoie la valeur mappée à cette clé. Sinon, la valeur par défaut est retournée. Votre code ne crée ni n'attribue jamais de valeur à clé. Dans cet exemple, défaut par défaut à Aucun.

Vous pouvez également utiliser des instructions conditionnelles pour gérer les clés manquantes dans les dictionnaires. Jetez un œil à l'exemple suivant, qui utilise le entrer le dict idiome:

>>>

>>> a_dict = 
>>> si 'clé' dans a_dict:
...     # Faites quelque chose avec 'clé' ...
...     a_dict[[[['clé']
... autre:
...     a_dict[[[['clé'] = 'valeur par défaut'
...
>>> a_dict
'clé': 'valeur par défaut'

Dans ce code, vous utilisez un si déclaration avec le dans opérateur pour vérifier si clé est présent dans a_dict. Si c'est le cas, vous pouvez effectuer n'importe quelle action avec clé ou avec sa valeur. Sinon, vous créez la nouvelle clé, cléet lui attribuer un 'valeur par défaut'. Notez que le code ci-dessus fonctionne de manière similaire à .définir par defaut() mais prend quatre lignes de code, tandis que .définir par defaut() ne prendrait qu'une seule ligne (en plus d'être plus lisible).

Vous pouvez également vous promener KeyError en utilisant un essayer et sauf bloc pour gérer l'exception. Considérez le morceau de code suivant:

>>>

>>> a_dict = 
>>> essayer:
...     # Faites quelque chose avec 'clé' ...
...     a_dict[[[['clé']
... sauf KeyError:
...     a_dict[[[['clé'] = 'valeur par défaut'
...
>>> a_dict
'clé': 'valeur par défaut'

le essayer et sauf bloc dans l'exemple ci-dessus attrape le KeyError chaque fois que vous essayez d'accéder à une clé manquante. dans le sauf clause, vous créez le clé et lui attribuer un 'valeur par défaut'.

Jusqu'à présent, vous avez appris à gérer les clés manquantes à l'aide des outils dicter et Python vous offre. Cependant, les exemples que vous avez vus ici sont assez verbeux et difficiles à lire. Ils peuvent ne pas être aussi simples que vous le souhaitez. C'est pourquoi la bibliothèque standard Python fournit une solution plus élégante, Pythonique et efficace. Cette solution est collections.defaultdict, et c'est ce que vous allez couvrir désormais.

Comprendre le Python defaultdict Type

La bibliothèque standard Python fournit collections, qui est un module qui implémente des types de conteneurs spécialisés. L'un d'eux est le Python defaultdict type, qui est une alternative à dicter spécialement conçu pour vous aider avec les clés manquantes. defaultdict est un type Python qui hérite de dicter:

>>>

>>> de collections importation defaultdict
>>> issubclass(defaultdict, dicter)
Vrai

Le code ci-dessus montre que le Python defaultdict le type est un sous-classe de dicter. Cela signifie que defaultdict hérite de la plupart du comportement de dicter. Vous pouvez donc dire que defaultdict ressemble beaucoup à un dictionnaire ordinaire.

La principale différence entre defaultdict et dicter est que lorsque vous essayez d'accéder ou de modifier un clé qui n'est pas présent dans le dictionnaire, une valeur par défaut valeur est automatiquement attribué à cette clé. Afin de fournir cette fonctionnalité, le Python defaultdict type fait deux choses:

  1. Il remplace .__disparu__().
  2. Il ajoute .default_factory, une variable d'instance accessible en écriture qui doit être fournie au moment de l'instanciation.

La variable d'instance .default_factory contiendra le premier argument passé dans defaultdict .__ init __ (). Cet argument peut prendre un appelable Python valide ou Aucun. Si un appelable est fourni, il sera automatiquement appelé par defaultdict chaque fois que vous essayez d'accéder ou de modifier la valeur associée à une clé manquante.

Découvrez comment créer et initialiser correctement un defaultdict:

>>>

>>> # Instanciation correcte
>>> def_dict = defaultdict(liste)  # Passer la liste à .default_factory
>>> def_dict[[[['un'] = 1  # Ajouter une paire valeur / clé
>>> def_dict[[[['disparu']  # Accéder à une clé manquante renvoie une liste vide
[]
>>> def_dict[[[['another_missing'].ajouter(4)  # Modifier une clé manquante
>>> def_dict
defaultdict (, 'un': 1, 'manquant': [], 'another_missing': [4])

Ici, tu passes liste à .default_factory lorsque vous créez le dictionnaire. Ensuite, vous utilisez def_dict comme un dictionnaire ordinaire. Notez que lorsque vous essayez d'accéder ou de modifier la valeur mappée à une clé inexistante, le dictionnaire lui attribue la valeur par défaut résultant de l'appel liste().

Gardez à l'esprit que vous devez transmettre un objet appelable Python valide à .default_factory, n'oubliez donc pas de l'appeler en utilisant les parenthèses au moment de l'initialisation. Cela peut être un problème courant lorsque vous commencez à utiliser Python defaultdict type. Jetez un œil au code suivant:

>>>

>>> # Mauvaise instanciation
>>> def_dict = defaultdict(liste())
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    def_dict = defaultdict(liste())
Erreur-type: le premier argument doit pouvoir être appelé ou Aucun

Ici, vous essayez de créer un defaultdict en passant liste() à .default_factory. L'appel à liste() soulève un Erreur-type, qui vous indique que le premier argument doit être appelable ou Aucun.

Avec cette introduction au Python defaultdict tapez, vous pouvez commencer à coder avec des exemples pratiques. Les sections suivantes vous guideront à travers quelques cas d’utilisation courants où vous pouvez defaultdict pour fournir une solution élégante, efficace et Pythonique.

Utiliser le Python defaultdict Type

Parfois, vous utiliserez une collection intégrée mutable (un liste, dicter, ou ensemble) comme valeurs dans vos dictionnaires Python. Dans ces cas, vous devrez initialiser les clés avant la première utilisation, ou vous obtiendrez un KeyError. Vous pouvez soit faire ce processus manuellement ou l'automatiser à l'aide d'un Python defaultdict. Dans cette section, vous apprendrez à utiliser Python defaultdict type pour résoudre certains problèmes de programmation courants:

  • Regroupement les éléments d'une collection
  • Compte les éléments d'une collection
  • Accumuler les valeurs d'une collection

Vous couvrirez quelques exemples qui utilisent liste, ensemble, int, et flotte pour effectuer des opérations de regroupement, de comptage et d'accumulation de manière conviviale et efficace.

Regroupement d'éléments

Une utilisation typique du Python defaultdict le type est à définir .default_factory à liste puis créez un dictionnaire qui mappe les clés aux listes de valeurs. Avec ça defaultdict, si vous essayez d'accéder à une clé manquante, le dictionnaire exécute les étapes suivantes:

  1. Appel liste() pour créer un nouveau vide liste
  2. Insérer le vide liste dans le dictionnaire en utilisant la clé manquante comme clé
  3. Revenir une référence à cela liste

Cela vous permet d'écrire du code comme ceci:

>>>

>>> de collections importation defaultdict
>>> dd = defaultdict(liste)
>>> dd[[[['clé'].ajouter(1)
>>> dd
defaultdict (, 'clé': [1])
>>> dd[[[['clé'].ajouter(2)
>>> dd
defaultdict (, 'clé': [1, 2])
>>> dd[[[['clé'].ajouter(3)
>>> dd
defaultdict (, 'clé': [1, 2, 3])

Ici, vous créez un Python defaultdict appelé dd et passer liste à .default_factory. Notez que même lorsque clé n'est pas défini, vous pouvez y ajouter des valeurs sans obtenir de KeyError. C'est parce que dd appelle automatiquement .default_factory pour générer une valeur par défaut pour le manquant clé.

Vous pouvez utiliser defaultdict de même que liste pour regrouper les éléments dans une séquence ou une collection. Supposons que vous ayez récupéré les données suivantes de la base de données de votre entreprise:

département Nom de l'employé
Ventes John Doe
Ventes Martin Smith
Comptabilité Jane Doe
Commercialisation Elizabeth Smith
Commercialisation Adam Doe

Avec ces données, vous créez une première liste de tuple des objets comme les suivants:

dep = [([([([('Ventes', «John Doe»),
       ('Ventes', «Martin Smith»),
       ('Comptabilité', 'Jane Doe'),
       ('Commercialisation', «Elizabeth Smith»),
       ('Commercialisation', «Adam Doe»)]

Maintenant, vous devez créer un dictionnaire qui regroupe les employés par service. Pour ce faire, vous pouvez utiliser un defaultdict comme suit:

de collections importation defaultdict

dep_dd = defaultdict(liste)
pour département, employé dans dep:
    dep_dd[[[[département].ajouter(employé)

Ici, vous créez un defaultdict appelé dep_dd et utiliser un pour boucle pour parcourir votre dep liste. La déclaration dep_dd[department].append (employé) crée les clés des départements, les initialise dans une liste vide, puis ajoute les employés à chaque département. Une fois que vous avez exécuté ce code, votre dep_dd ressemblera à ceci:

>>>

defaultdict (, 'Ventes': ['John Doe', 'Martin Smith'],
                                                                                                                    'Comptabilité' : ['Jane Doe'],
                                                                                                                    'Commercialisation': ['Elizabeth Smith', 'Adam Doe'])

Dans cet exemple, vous regroupez les employés par service en utilisant un defaultdict avec .default_factory mis à liste. Pour ce faire avec un dictionnaire standard, vous pouvez utiliser dict.setdefault () comme suit:

dep_d = dicter()
pour département, employé dans dep:
    dep_d.définir par defaut(département, []).ajouter(employé)

Ce code est simple et vous trouverez assez souvent du code similaire dans votre travail de codeur Python. Cependant, le defaultdict La version est sans doute plus lisible, et pour les grands ensembles de données, elle peut également être beaucoup plus rapide et plus efficace. Donc, si la vitesse vous préoccupe, vous devriez envisager d'utiliser un defaultdict au lieu d'une norme dicter.

Regroupement d'articles uniques

Continuez à travailler avec les données des départements et des employés de la section précédente. Après un certain traitement, vous vous rendez compte que quelques employés ont été dupliqué dans la base de données par erreur. Vous devez nettoyer les données et supprimer les employés en double de votre dep_dd dictionnaire. Pour ce faire, vous pouvez utiliser un ensemble comme le .default_factory et réécrivez votre code comme suit:

dep = [([([([('Ventes', «John Doe»),
       ('Ventes', «Martin Smith»),
       ('Comptabilité', 'Jane Doe'),
       ('Commercialisation', «Elizabeth Smith»),
       ('Commercialisation', «Elizabeth Smith»),
       ('Commercialisation', «Adam Doe»),
       ('Commercialisation', «Adam Doe»),
       ('Commercialisation', «Adam Doe»)]

dep_dd = defaultdict(ensemble)
pour département, employé dans articles:
    dep_dd[[[[département].ajouter(employé)

Dans cet exemple, vous définissez .default_factory à ensemble. Ensembles sont collections d'objets uniques, ce qui signifie que vous ne pouvez pas créer un ensemble avec des éléments répétés. Il s'agit d'une fonctionnalité très intéressante des ensembles, qui garantit que vous n'aurez pas d'éléments répétés dans votre dictionnaire final.

Compter les articles

Si vous définissez .default_factory à int, alors votre defaultdict sera utile pour compter les articles dans une séquence ou une collection. Quand vous appelez int () sans arguments, la fonction retourne 0, qui est la valeur typique que vous utilisez pour initialiser un compteur.

Pour continuer avec l'exemple de la base de données de l'entreprise, supposons que vous souhaitiez créer un dictionnaire qui compte le nombre d'employés par service. Dans ce cas, vous pouvez coder quelque chose comme ceci:

>>>

>>> de collections importation defaultdict
>>> dep = [([([([('Ventes', «John Doe»),
...        ('Ventes', «Martin Smith»),
...        ('Comptabilité', 'Jane Doe'),
...        ('Commercialisation', «Elizabeth Smith»),
...        ('Commercialisation', «Adam Doe»)]
>>> dd = defaultdict(int)
>>> pour département, _ dans dep:
...     dd[[[[département] + = 1
>>> dd
defaultdict (, 'Ventes': 2, 'Comptabilité': 1, 'Marketing': 2)

Ici, vous définissez .default_factory à int. Quand vous appelez int () sans argument, la valeur renvoyée est 0. Vous pouvez utiliser cette valeur par défaut pour commencer à compter les employés qui travaillent dans chaque service. Pour que ce code fonctionne correctement, vous avez besoin d'un ensemble de données propre. Il ne doit pas y avoir de données répétées. Sinon, vous devrez filtrer les employés répétés.

Un autre exemple de comptage des éléments est le Mississippi exemple, où vous comptez le nombre de fois que chaque lettre d'un mot est répétée. Jetez un œil au code suivant:

>>>

>>> de collections importation defaultdict
>>> s = 'Mississippi'
>>> dd = defaultdict(int)
>>> pour lettre dans s:
...     dd[[[[lettre] + = 1
...
>>> dd
defaultdict (, 'm': 1, 'i': 4, 's': 4, 'p': 2)

Dans le code ci-dessus, vous créez un defaultdict avec .default_factory mis à int. Cela définit la valeur par défaut d'une clé donnée sur 0. Ensuite, vous utilisez un pour boucle pour parcourir la chaîne s et utiliser une opération d'affectation augmentée pour ajouter 1 au comptoir à chaque itération. Les clés de dd seront les lettres Mississippi.

Comme le comptage est une tâche relativement courante en programmation, la classe de type dictionnaire Python collections.Counter est spécialement conçu pour compter les articles dans une séquence. Avec Compteur, vous pouvez écrire le Mississippi exemple comme suit:

>>>

>>> de collections importation Compteur
>>> compteur = Compteur('Mississippi')
>>> compteur
Compteur ('i': 4, 's': 4, 'p': 2, 'm': 1)

Dans ce cas, Compteur fait tout le travail pour vous! Vous n'avez qu'à passer dans une séquence, et le dictionnaire comptera ses éléments, les stockant comme clés et les comptes comme valeurs. Notez que cet exemple fonctionne car les chaînes Python sont également un type de séquence.

Accumuler des valeurs

Parfois, vous devrez calculer le somme totale des valeurs dans une séquence ou une collection. Supposons que vous ayez la feuille Excel suivante avec des données sur les ventes de votre site Web Python:

Des produits juillet août septembre
Livres 1250,00 1300,00 1420,00
Tutoriels 560,00 630,00 750,00
Cours 2500,00 2430.00 2750,00

Ensuite, vous traitez les données à l'aide de Python et obtenez les éléments suivants liste de tuple objets:

revenus = [([([([('Livres', 1250,00),
           ('Livres', 1300,00),
           ('Livres', 1420,00),
           ('Tutoriels', 560,00),
           ('Tutoriels', 630,00),
           ('Tutoriels', 750,00),
           ('Cours', 2500,00),
           ('Cours', 2430.00),
           ('Cours', 2750,00),]

Avec ces données, vous souhaitez calculer le revenu total par produit. Pour ce faire, vous pouvez utiliser un Python defaultdict avec flotte comme .default_factory puis coder quelque chose comme ceci:

    1 de collections importation defaultdict
    2 
    3 dd = defaultdict(flotte)
    4 pour produit, le revenu dans revenus:
    5     dd[[[[produit] + = le revenu
    6 
    sept pour produit, le revenu dans dd.articles():
    8     impression(F«Revenu total pour produit: $revenus:,. 2f")

Voici ce que fait ce code:

  • En ligne 1, vous importez le Python defaultdict type.
  • En ligne 3, vous créez un defaultdict objet avec .default_factory mis à flotte.
  • En ligne 4, vous définissez un pour boucle pour parcourir les éléments de revenus.
  • En ligne 5, vous utilisez une opération d'affectation augmentée (+ =) pour accumuler les revenus par produit dans le dictionnaire.

La deuxième boucle parcourt les éléments de dd et imprime les revenus sur votre écran.

Si vous mettez tout ce code dans un fichier appelé revenus.py et l'exécuter à partir de votre ligne de commande, vous obtiendrez la sortie suivante:

$ python3 revenus.py
Revenu total pour les livres: 3 970,00 $
Revenu total pour les tutoriels: 1 940,00 $
Revenu total pour les cours: 7 680,00 $

Vous avez maintenant un résumé des revenus par produit, vous pouvez donc prendre des décisions sur la stratégie à suivre pour augmenter le revenu total de votre site.

Plonger plus profondément defaultdict

Jusqu'à présent, vous avez appris à utiliser le Python defaultdict tapez en codant quelques exemples pratiques. À ce stade, vous pouvez plonger plus profondément dans implémentation de type et d'autres détails de travail. C'est ce que vous couvrirez dans les prochaines sections.

defaultdict contre dicter

Pour que vous compreniez mieux le Python defaultdict type, un bon exercice serait de le comparer avec sa superclasse, dicter. Si vous voulez connaître les méthodes et les attributs spécifiques à Python defaultdict , vous pouvez exécuter la ligne de code suivante:

>>>

>>> ensemble(dir(defaultdict)) - ensemble(dir(dicter))
'__copy__', 'default_factory', '__missing__'

Dans le code ci-dessus, vous utilisez dir () pour obtenir la liste des attributs valides pour dicter et defaultdict. Ensuite, vous utilisez un ensemble différence pour obtenir l'ensemble des méthodes et des attributs que vous ne pouvez trouver que dans defaultdict. Comme vous pouvez le voir, les différences entre ces deux classes sont. Vous avez deux méthodes et un attribut d'instance. Le tableau suivant montre à quoi servent les méthodes et l'attribut:

Méthode ou attribut La description
.__copie__() Fournit un support pour copy.copy ()
.default_factory Contient l'appelable appelé par .__disparu__() pour fournir automatiquement des valeurs par défaut pour les clés manquantes
.__ manquant __ (clé) Obtient appelé quand .__obtenir l'article__() ne trouve pas clé

Dans le tableau ci-dessus, vous pouvez voir les méthodes et l'attribut qui font un defaultdict différent d'un habitué dicter. Les autres méthodes sont les mêmes dans les deux classes.

En outre, vous remarquerez peut-être qu'un defaultdict est égal à dicter avec les mêmes éléments:

>>>

>>> std_dict = dicter(Nombres=[[[[1, 2, 3], des lettres=[[[['une', «b», «c»])
>>> std_dict
'Nombres': [1, 2, 3], 'des lettres': ['a', 'b', 'c']
>>> def_dict = defaultdict(liste, Nombres=[[[[1, 2, 3], des lettres=[[[['une', «b», «c»])
>>> def_dict
defaultdict (, 'Nombres': [1, 2, 3], 'des lettres': ['a', 'b', 'c'])
>>> std_dict == def_dict
Vrai

Ici, vous créez un dictionnaire régulier std_dict avec quelques éléments arbitraires. Ensuite, vous créez un defaultdict avec les mêmes éléments. Si vous testez l’égalité de contenu des deux dictionnaires, vous verrez qu’ils sont égaux.

defaultdict.default_factory

Le premier argument du Python defaultdict le type doit être un appelable qui ne prend aucun argument et renvoie une valeur. Cet argument est affecté à l'attribut d'instance, .default_factory. Pour cela, vous pouvez utiliser tout appelable, y compris les fonctions, méthodes, classes, objets de type ou tout autre appelable valide. La valeur par défaut de .default_factory est Aucun.

Si vous instanciez defaultdict sans passer une valeur à .default_factory, le dictionnaire se comportera comme un habitué dicter et l'habituel KeyError sera levée pour les tentatives de recherche ou de modification de clés manquantes:

>>>

>>> de collections importation defaultdict
>>> dd = defaultdict()
>>> dd[[[['missing_key']
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    dd[[[['missing_key']
KeyError: 'missing_key'

Ici, vous instanciez le Python defaultdict tapez sans arguments. Dans ce cas, l'instance se comporte comme un dictionnaire standard. Donc, si vous essayez d'accéder ou de modifier une clé manquante, vous obtiendrez alors l'habituel KeyError. À partir de ce moment, vous pouvez utiliser dd comme un dictionnaire Python normal et, sauf si vous attribuez un nouveau callable à .default_factory, vous ne pourrez pas utiliser la capacité de defaultdict pour gérer automatiquement les clés manquantes.

Si vous passez Aucun au premier argument de defaultdict, l'instance se comportera de la même manière que vous l'avez vu dans l'exemple ci-dessus. C'est parce que .default_factory par défaut à Aucun, les deux initialisations sont donc équivalentes. D'un autre côté, si vous passez un objet appelable valide à .default_factory, vous pouvez ensuite l'utiliser pour gérer les clés manquantes de manière conviviale. Voici un exemple où vous passez liste à .default_factory:

>>>

>>> dd = defaultdict(liste, des lettres=[[[['une', «b», «c»])
>>> dd.default_factory

>>> dd
defaultdict (, 'des lettres': ['a', 'b', 'c'])
>>> dd[[[['Nombres']
[]
>>> dd
defaultdict (, 'des lettres': ['a', 'b', 'c'], 'Nombres': [])
>>> dd[[[['Nombres'].ajouter(1)
>>> dd
defaultdict (, 'des lettres': ['a', 'b', 'c'], 'Nombres': [1])
>>> dd[[[['Nombres'] + = [[[[2, 3]
>>> dd
defaultdict (, 'des lettres': ['a', 'b', 'c'], 'Nombres': [1, 2, 3])

Dans cet exemple, vous créez un Python defaultdict appelé dd, alors vous utilisez liste pour son premier argument. Le deuxième argument est appelé des lettres et détient une liste de lettres. Tu vois ça .default_factory détient maintenant un liste objet qui sera appelé lorsque vous devrez fournir une valeur par défaut valeur pour toute clé manquante.

Notez que lorsque vous essayez d'accéder à Nombres, dd teste si Nombres est dans le dictionnaire. Si ce n'est pas le cas, il appelle .default_factory (). Puisque .default_factory détient un liste objet, le retourné valeur est une liste vide ([]).

Maintenant que dd['numbers'] est initialisé avec un vide liste, vous pouvez utiliser .ajouter() pour ajouter des éléments à la liste. Vous pouvez également utiliser un opérateur d'affectation augmenté (+ =) pour concaténer les listes [1] et [2, 3]. De cette façon, vous pouvez gérer les clés manquantes de manière plus Pythonic et plus efficace.

D'un autre côté, si vous passez un non appelable objet à l'initialiseur du Python defaultdict tapez, vous obtiendrez un Erreur-type comme dans le code suivant:

>>>

>>> defaultdict(0)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    defaultdict(0)
Erreur-type: le premier argument doit pouvoir être appelé ou Aucun

Ici, tu passes 0 à .default_factory. Puisque 0 n'est pas un objet appelable, vous obtenez un Erreur-type vous dire que le premier argument doit être appelable ou Aucun. Autrement, defaultdict ne fonctionne pas.

Garde en tête que .default_factory est uniquement appelé depuis .__obtenir l'article__() et pas d'autres méthodes. Cela signifie que si dd est un defaultdict et clé est une clé manquante, alors dd[key] appellera .default_factory pour fournir une valeur par défaut valeur, mais dd.get (clé) revient toujours Aucun au lieu de la valeur .default_factory fournirait. C'est parce que .avoir() n'appelle pas .__obtenir l'article__() pour récupérer le clé.

Jetez un œil au code suivant:

>>>

>>> dd = defaultdict(liste)
>>> # Appelle jj .__ getitem __ ('manquant')
>>> dd[[[['disparu']
[]
>>> # N'appelez pas dd .__ getitem __ ('another_missing')
>>> impression(dd.avoir('another_missing'))
Aucun
>>> dd
defaultdict (, 'disparu': [])

Dans ce fragment de code, vous pouvez voir que dd.get () Retour Aucun plutôt que la valeur par défaut .default_factory fournirait. C'est parce que .default_factory est uniquement appelé depuis .__disparu__(), qui n'est pas appelé par .avoir().

Notez que vous pouvez également ajouter valeurs arbitraires à un Python defaultdict. Cela signifie que vous n'êtes pas limité aux valeurs du même type que les valeurs générées par .default_factory. Voici un exemple:

>>>

>>> dd = defaultdict(liste)
>>> dd
defaultdict (, )
>>> dd[[[['chaîne'] = «une chaîne»
>>> dd
defaultdict (, 'chaîne': 'une chaîne')
>>> dd[[[['liste']
[]
>>> dd
defaultdict (, 'chaîne': 'une chaîne', 'liste': [])

Ici, vous créez un defaultdict et passer dans un liste s'opposer à .default_factory. Cela définit vos valeurs par défaut comme des listes vides. Cependant, vous pouvez librement ajouter une nouvelle clé contenant des valeurs d'un type différent. C’est le cas avec la clé chaîne, qui détient un str objet au lieu d'un liste objet.

Enfin, vous pouvez toujours changer ou mettre à jour l'appelable vous attribuez initialement à .default_factory de la même manière que vous le feriez avec n'importe quel attribut d'instance:

>>>

>>> dd.default_factory = str
>>> dd[[[['missing_key']
''

Dans le code ci-dessus, vous modifiez .default_factory de liste à str. Maintenant, chaque fois que vous essayez d'accéder à une clé manquante, votre valeur par défaut sera une chaîne vide ('').

Selon vos cas d'utilisation pour Python defaultdict tapez, vous devrez peut-être geler le dictionnaire une fois que vous aurez fini de le créer et de le rendre en lecture seule. Pour ce faire, vous pouvez définir .default_factory à Aucun après avoir rempli le dictionnaire. De cette façon, votre dictionnaire se comportera comme un standard dicter, ce qui signifie que vous n'aurez plus de valeurs par défaut générées automatiquement.

defaultdict contre dict.setdefault ()

Comme vous l'avez vu auparavant, dicter fournit .définir par defaut(), ce qui vous permettra d'attribuer des valeurs aux clés manquantes à la volée. En revanche, avec un defaultdict vous pouvez spécifier la valeur par défaut à l'avance lorsque vous initialisez le conteneur. Vous pouvez utiliser .définir par defaut() pour affecter les valeurs par défaut comme suit:

>>>

>>>  = dicter()
>>> .définir par defaut('missing_key', [])
[]
>>> 
'missing_key': []

Dans ce code, vous créez un dictionnaire standard, puis utilisez .définir par defaut() pour attribuer une valeur ([]) à la clé clé_ manquante, qui n'était pas encore défini.

En revanche, si vous utilisez un defaultdict pour accomplir la même tâche, la valeur par défaut est générée à la demande chaque fois que vous essayez d'accéder ou de modifier une clé manquante. Notez qu'avec defaultdict, la valeur par défaut est générée par l'appelable que vous transmettez en amont à l'initialiseur de la classe. Voici comment ça fonctionne:

>>>

>>> de collections importation defaultdict
>>> dd = defaultdict(liste)
>>> dd[[[['missing_key']
[]
>>> dd
defaultdict (, 'missing_key': [])

Ici, vous importez d'abord le Python defaultdict taper de collections. Ensuite, vous créez un defaultdict et passer liste à .default_factory. Lorsque vous essayez d'accéder à une clé manquante, defaultdict appels internes .default_factory (), qui contient une référence à listeet attribue la valeur résultante (une valeur vide liste) à clé_ manquante.

Le code dans les deux exemples ci-dessus fait le même travail, mais le defaultdict la version est sans doute plus lisible, conviviale, Pythonic et simple.

Enfin, en utilisant un defaultdict gérer les clés manquantes peut être plus rapide que d'utiliser dict.setdefault (). Jetez un œil à l'exemple suivant:

# Nom de fichier: exec_time.py

de collections importation defaultdict
de timeit importation timeit

animaux = [([([([('chat', 1), ('lapin', 2), ('chat', 3), ('chien', 4), ('dog', 1)]
std_dict = dict()
def_dict = defaultdict(list)

def group_with_dict():
    pour animal, count dans animals:
        std_dict.setdefault(animal, []).append(count)
    revenir std_dict

def group_with_defaultdict():
    pour animal, count dans animals:
        def_dict[[[[animal].append(count)
    revenir def_dict

impression(f'dict.setdefault() takes timeit(group_with_dict) seconds.')
impression(f'defaultdict takes timeit(group_with_defaultdict) seconds.')

If you run the script from your system’s command line, then you’ll get something like this:

$ python3 exec_time.py
dict.setdefault() takes 1.0281260240008123 seconds.
defaultdict takes 0.6704721650003194 seconds.

Here, you use timeit.timeit() to measure the execution time of group_with_dict() et group_with_defaultdict(). These functions perform equivalent actions, but the first uses dict.setdefault(), and the second uses a defaultdict. The time measure will depend on your current hardware, but you can see here that defaultdict is faster than dict.setdefault(). This difference can become more important as the dataset gets larger.

Additionally, you need to consider that creating a regular dict can be faster than creating a defaultdict. Take a look at this code:

>>>

>>> de timeit import timeit
>>> de collections import defaultdict
>>> impression(f'dict() takes timeit(dict) seconds.')
dict() takes 0.08921320698573254 seconds.
>>> impression(f'defaultdict() takes timeit(defaultdict) seconds.')
defaultdict() takes 0.14101867799763568 seconds.

This time, you use timeit.timeit() to measure the execution time of dict et defaultdict instantiation. Notice that creating a dict takes almost half the time of creating a defaultdict. This might not be a problem if you consider that, in real-world code, you normally instantiate defaultdict only once.

Also notice that, by default, timeit.timeit() will run your code a million times. That’s the reason for defining std_dict et def_dict out of the scope of group_with_dict() et group_with_defaultdict() dans exec_time.py. Otherwise, the time measure will be affected by the instantiation time of dict et defaultdict.

At this point, you may have an idea of when to use a defaultdict rather than a regular dict. Here are three things to take into account:

  1. If your code is heavily base on dictionaries and you’re dealing with missing keys all the time, then you should consider using a defaultdict rather than a regular dict.

  2. If your dictionary items need to be initialized with a constant default value, then you should consider using a defaultdict instead of a dict.

  3. If your code relies on dictionaries for aggregating, accumulating, counting, or grouping values, and performance is a concern, then you should consider using a defaultdict.

You can consider the above guidelines when deciding whether to use a dict ou un defaultdict.

defaultdict.__missing__()

Behind the scenes, the Python defaultdict type works by calling .default_factory to supply default values to missing keys. The mechanism that makes this possible is .__missing__(), a special method supported by all the standard mapping types, including dict et defaultdict.

So, how does .__missing__() work? If you set .default_factory à Aucun, then .__missing__() raises a KeyError with the key as an argument. Autrement, .default_factory is called without arguments to provide a default value for the given key. Cette value is inserted into the dictionary and finally returned. If calling .default_factory raises an exception, then the exception is propagated unchanged.

The following code shows a viable Python implementation for .__missing__():

    1 def __missing__(self, key):
    2     si self.default_factory est Aucun:
    3         raise KeyError(key)
    4     si key ne pas dans self:
    5         self[[[[key] = self.default_factory()
    6     revenir self[[[[key]

Here’s what this code does:

  • In line 1, you define the method and its signature.
  • In lines 2 and 3, you test to see if .default_factory est Aucun. If so, then you raise a KeyError with the key as an argument.
  • In lines 4 and 5, you check if the key is not in the dictionary. If it’s not, then you call .default_factory and assign its return value to the key.
  • In line 6, you return the key as expected.

Keep in mind that the presence of .__missing__() in a mapping has no effect on the behavior of other methods that look up keys, such as .get() ou .__contains__(), which implements the dans operator. C'est parce que .__missing__() is only called by .__getitem__() when the requested key is not found in the dictionary. Peu importe .__missing__() returns or raises is then returned or raised by .__getitem__().

Now that you’ve covered an alternative Python implementation for .__missing__(), it would be a good exercise to try to emulate defaultdict with some Python code. That’s what you’ll be doing in the next section.

Emulating the Python defaultdict Type

In this section, you’ll be coding a Python class that will behave much like a defaultdict. To do that, you’ll subclass collections.UserDict and then add .__missing__(). Also, you need to add an instance attribute called .default_factory, which will hold the callable for generating default values on demand. Here’s a piece of code that emulates most of the behavior of the Python defaultdict type:

    1 import collections
    2 
    3 class my_defaultdict(collections.UserDict):
    4     def __init__(self, default_factory=Aucun, *args, **kwargs):
    5         super().__init__(*args, **kwargs)
    6         si ne pas callable(default_factory) et default_factory est ne pas Aucun:
    sept             raise TypeError('first argument must be callable or None')
    8         self.default_factory = default_factory
    9 
dix     def __missing__(self, key):
11         si self.default_factory est Aucun:
12             raise KeyError(key)
13         si key ne pas dans self:
14             self[[[[key] = self.default_factory()
15         revenir self[[[[key]

Here’s how this code works:

  • In line 1, you import collections to get access to UserDict.

  • In line 3, you create a class that subclasses UserDict.

  • In line 4, you define the class initializer .__init__(). This method takes an argument called default_factory to hold the callable that you’ll use to generate the default values. Notice that default_factory defaults to Aucun, just like in a defaultdict. You also need the *args et **kwargs for emulating the normal behavior of a regular dict.

  • In line 5, you call the superclass .__init__(). This means that you’re calling UserDict.__init__() and passing *args et **kwargs to it.

  • In line 6, you first check if default_factory is a valid callable object. In this case, you use callable(object), which is a built-in function that returns True si object appears to be a callable and otherwise returns False. This check ensures that you can call .default_factory() if you need to generate a default value for any missing key. Then, you check if .default_factory is not Aucun.

  • In line 7, you raise a TypeError just like a regular dict would do if default_factory est Aucun.

  • In line 8, you initialize .default_factory.

  • In line 10, you define .__missing__(), which is implemented as you saw before. Recall that .__missing__() is automatically called by .__getitem__() when a given key is not in a dictionary.

If you feel in the mood to read some C code, then you can take a look at the full code for the Python defaultdict Type in the CPython source code.

Now that you’ve finished coding this class, you can test it by putting the code into a Python script called my_dd.py and importing it from an interactive session. Here’s an example:

>>>

>>> de my_dd import my_defaultdict
>>> dd_one = my_defaultdict(list)
>>> dd_one

>>> dd_one[[[['missing']
[]
>>> dd_one
'missing': []
>>> dd_one.default_factory = int
>>> dd_one[[[['another_missing']
0
>>> dd_one
'missing': [], 'another_missing': 0
>>> dd_two = my_defaultdict(Aucun)
>>> dd_two[[[['missing']
Traceback (most recent call last):
  Fichier "", line 1, dans 
    dd_two[[[['missing']
        File "/home/user/my_dd.py", line 10,
    in __missing__
                raise KeyError(key)
KeyError: 'missing'

Here, you first import my_defaultdict de my_dd. Then, you create an instance of my_defaultdict and pass list à .default_factory. If you try to get access to a key with a subscription operation, like dd_one['missing'], then .__getitem__() is automatically called by Python. If the key is not in the dictionary, then .__missing__() is called, which generates a default value by calling .default_factory().

You can also change the callable assigned to .default_factory using a normal assignment operation like in dd_one.default_factory = int. Finally, if you pass Aucun à .default_factory, then you’ll get a KeyError when trying to retrieve a missing key.

You may be wondering why you subclass collections.UserDict instead of a regular dict pour cet exemple. The main reason for this is that subclassing built-in types can be error-prone because the C code of the built-ins doesn’t seem to consistently call special methods overridden by the user.

Here’s an example that shows some issues that you can face when subclassing dict:

>>>

>>> class MyDict(dict):
...     def __setitem__(self, key, value):
...         super().__setitem__(key, Aucun)
...
>>> my_dict = MyDict(premier=1)
>>> my_dict
'first': 1
>>> my_dict[[[['second'] = 2
>>> my_dict
'first': 1, 'second': None
>>> my_dict.setdefault('third', 3)
3
>>> my_dict
'first': 1, 'second': None, 'third': 3

In this example, you create MyDict, which is a class that subclasses dict. Your implementation of .__setitem__() always sets values to Aucun. If you create an instance of MyDict and pass a keyword argument to its initializer, then you’ll notice the class is not calling your .__setitem__() to handle the assignment. You know that because the key premier wasn’t assigned Aucun.

By contrast, if you run a subscription operation like my_dict['second'] = 2, then you’ll notice that seconde is set to Aucun rather than to 2. So, this time you can say that subscription operations call your custom .__setitem__(). Finally, notice that .setdefault() doesn’t call .__setitem__() either, because your troisième key ends up with a value of 3.

UserDict doesn’t inherit from dict but simulates the behavior of a standard dictionary. The class has an internal dict instance called .data, which is used to store the content of the dictionary. UserDict is a more reliable class when it comes to creating custom mappings. If you use UserDict, then you’ll be avoiding the issues you saw before. To prove this, go back to the code for my_defaultdict and add the following method:

    1 class my_defaultdict(collections.UserDict):
    2     # Snip
    3     def __setitem__(self, key, value):
    4         impression('__setitem__() gets called')
    5         super().__setitem__(key, Aucun)

Here, you add a custom .__setitem__() that calls the superclass .__setitem__(), which always sets the value to Aucun. Update this code in your script my_dd.py and import it from an interactive session as follows:

>>>

>>> de my_dd import my_defaultdict
>>> my_dict = my_defaultdict(list, premier=1)
__setitem__() gets called
>>> my_dict
'first': None
>>> my_dict[[[['second'] = 2
__setitem__() gets called
>>> my_dict
'first': None, 'second': None

In this case, when you instantiate my_defaultdict and pass premier to the class initializer, your custom __setitem__() gets called. Also, when you assign a value to the key seconde, __setitem__() gets called as well. You now have a my_defaultdict that consistently calls your custom special methods. Notice that all the values in the dictionary are equal to Aucun maintenant.

Passing Arguments to .default_factory

As you saw earlier, .default_factory must be set to a callable object that takes no argument and returns a value. This value will be used to supply a default value for any missing key in the dictionary. Even when .default_factory shouldn’t take arguments, Python offers some tricks that you can use if you need to supply arguments to it. In this section, you’ll cover two Python tools that can serve this purpose:

  1. lambda
  2. functools.partial()

With these two tools, you can add extra flexibility to the Python defaultdict type. For example, you can initialize a defaultdict with a callable that takes an argument and, after some processing, you can update the callable with a new argument to change the default value for the keys you’ll create from this point on.

Using lambda

A flexible way to pass arguments to .default_factory is to use lambda. Suppose you want to create a function to generate default values in a defaultdict. The function does some processing and returns a value, but you need to pass an argument for the function to work correctly. Here’s an example:

>>>

>>> def factory(arg):
...     # Do some processing here...
...     result = arg.upper()
...     revenir result
...
>>> def_dict = defaultdict(lambda: factory('default value'))
>>> def_dict[[[['missing']
'DEFAULT VALUE'

In the above code, you create a function called factory(). The function takes an argument, does some processing, and returns the final result. Then, you create a defaultdict and use lambda to pass the string 'default value' à factory(). When you try to get access to a missing key, the following steps are run:

  1. The dictionary def_dict calls its .default_factory, which holds a reference to a lambda function.
  2. le lambda function gets called and returns the value that results from calling factory() with 'default value' as an argument.

If you’re working with def_dict and suddenly need to change the argument to factory(), then you can do something like this:

>>>

>>> def_dict.default_factory = factory('another default value')
>>> def_dict[[[['another_missing']
'ANOTHER DEFAULT VALUE'

This time, factory() takes a new string argument ('another default value'). From now on, if you try to access or modify a missing key, then you’ll get a new default value, which is the string 'ANOTHER DEFAULT VALUE'.

Finally, you can possibly face a situation where you need a default value that’s different from 0 ou []. In this case, you can also use lambda à generate a different default value. For example, suppose you have a list of integer numbers, and you need to calculate the cumulative product of each number. Then, you can use a defaultdict de même que lambda comme suit:

>>>

>>> de collections import defaultdict
>>> lst = [[[[1, 1, 2, 1, 2, 2, 3, 4, 3, 3, 4, 4]
>>> def_dict = defaultdict(lambda: 1)
>>> pour nombre dans lst:
...     def_dict[[[[nombre] *= nombre
...
>>> def_dict
defaultdict(<function  at 0x...70>, 1: 1, 2: 8, 3: 27, 4: 64)

Here, you use lambda to supply a default value of 1. With this initial value, you can calculate the cumulative product of each number in lst. Notice that you can’t get the same result using int because the default value returned by int is always 0, which is not a good initial value for the multiplication operations you need to perform here.

Using functools.partial()

functools.partial(func, *args, **keywords) is a function that returns a partial object. When you call this object with the positional arguments (args) and keyword arguments (keywords), it behaves similar to when you call func(*args, **keywords). You can take advantage of this behavior of partial() and use it to pass arguments to .default_factory in a Python defaultdict. Here’s an example:

>>>

>>> def factory(arg):
...     # Do some processing here...
...     result = arg.upper()
...     revenir result
...
>>> de functools import partial
>>> def_dict = defaultdict(partial(factory, 'default value'))
>>> def_dict[[[['missing']
'DEFAULT VALUE'
>>> def_dict.default_factory = partial(factory, 'another default value')
>>> def_dict[[[['another_missing']
'ANOTHER DEFAULT VALUE'

Here, you create a Python defaultdict and use partial() to supply an argument to .default_factory. Notice that you can also update .default_factory to use another argument for the callable factory(). This kind of behavior can add a lot of flexibility to your defaultdict objects.

Conclusion

The Python defaultdict type is a dictionary-like data structure provided by the Python standard library in a module called collections. The class inherits from dict, and its main added functionality is to supply default values for missing keys. In this tutorial, you’ve learned how to use the Python defaultdict type for handling the missing keys in a dictionary.

You’re now able to:

  • Create and use a Python defaultdict to handle missing keys
  • Solve real-world problems related to grouping, counting, and accumulating operations
  • Know the implementation differences between defaultdict et dict
  • Decide when and why to use a Python defaultdict rather than a standard dict

The Python defaultdict type is a convenient and efficient data structure that’s designed to help you out when you’re dealing with missing keys in a dictionary. Give it a try and make your code faster, more readable, and more Pythonic!