Je veux créer un dictionnaire python qui me renvoie la valeur de clé pour les clés manquantes dans le dictionnaire.
Exemple d'utilisation:
dic = smart_dict()
dic['a'] = 'one a'
print(dic['a'])
# >>> one a
print(dic['b'])
# >>> b
dict
s ont un __missing__
crochet pour cela:
class smart_dict(dict):
def __missing__(self, key):
return key
Pourquoi n'utilisez-vous pas
dic.get('b', 'b')
Bien sûr, vous pouvez sous-classer dict
comme d'autres le soulignent, mais je trouve utile de me rappeler de temps en temps que get
peut avoir une valeur par défaut!
Si vous voulez essayer le defaultdict
, essayez ceci:
dic = defaultdict()
dic.__missing__ = lambda key: key
dic['b'] # should set dic['b'] to 'b' and return 'b'
sauf ... eh bien: AttributeError: ^collections.defaultdict^object attribute '__missing__' is read-only
, vous devrez donc sous-classer:
from collections import defaultdict
class KeyDict(defaultdict):
def __missing__(self, key):
return key
d = KeyDict()
print d['b'] #prints 'b'
print d.keys() #prints []
Le premier répondant a mentionné defaultdict
, mais vous pouvez définir __missing__
pour toute sous-classe de dict
:
>>> class Dict(dict):
def __missing__(self, key):
return key
>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d['z']
'z'
De plus, j'aime l'approche du deuxième répondant:
>>> d = dict(a=1, b=2)
>>> d.get('z', 'z')
'z'
Félicitations. Vous aussi, vous avez découvert l'inutilité du type standard collections.defaultdict
. Si ce tas caché exécrable de odeur de code offense autant votre sensibilité délicate que la mienne, c'est votre jour de chance StackOverflow.
Grâce à la merveille interdite de la variante à 3 paramètres du composant intégré type()
, créer un type de dictionnaire par défaut non inutile est à la fois amusant et rentable.
Absolument rien, en supposant que vous aimez l'excès de passe-partout et la stupidité stupéfiante de collections.defaultdict
- qui devrait se comporter comme prévu mais ne le fait vraiment pas. Pour être juste, Jochen Ritzel s solution acceptée de sous-classement dict
et implémentant méthode __missing__()
facultative est une solution de contournement fantastique pour les cas d'utilisation à petite échelle ne nécessitant qu'un seul dictionnaire par défaut.
Mais le passe-partout de cette sorte évolue mal. Si vous vous retrouvez en train d'instancier plusieurs dictionnaires par défaut, chacun avec sa propre logique légèrement différente pour générer des paires clé-valeur manquantes, une solution de rechange automatisée de puissance industrielle est garantie.
Ou du moins Nice. Parce que pourquoi ne pas réparer ce qui est cassé?
En moins de dix lignes de pur Python (hors docstrings, commentaires et espaces blancs), nous définissons maintenant un type DefaultDict
initialisé avec un appelable défini par l'utilisateur générant des valeurs par défaut pour les manquants Alors que l'appelable passé au type standard collections.defaultdict
accepte inutilement les paramètres no, l'appelable passé à notre type DefaultDict
accepte utilement les deux paramètres suivants:
Étant donné ce type, la résolution de la question de sorin se réduit à une seule ligne de Python:
>>> dic = DefaultDict(lambda self, missing_key: missing_key)
>>> dic['a'] = 'one a'
>>> print(dic['a'])
one a
>>> print(dic['b'])
b
Santé mentale. Enfin.
def DefaultDict(keygen):
'''
Sane **default dictionary** (i.e., dictionary implicitly mapping a missing
key to the value returned by a caller-defined callable passed both this
dictionary and that key).
The standard :class:`collections.defaultdict` class is sadly insane,
requiring the caller-defined callable accept *no* arguments. This
non-standard alternative requires this callable accept two arguments:
#. The current instance of this dictionary.
#. The current missing key to generate a default value for.
Parameters
----------
keygen : CallableTypes
Callable (e.g., function, lambda, method) called to generate the default
value for a "missing" (i.e., undefined) key on the first attempt to
access that key, passed first this dictionary and then this key and
returning this value. This callable should have a signature resembling:
``def keygen(self: DefaultDict, missing_key: object) -> object``.
Equivalently, this callable should have the exact same signature as that
of the optional :meth:`dict.__missing__` method.
Returns
----------
MappingType
Empty default dictionary creating missing keys via this callable.
'''
# Global variable modified below.
global _DEFAULT_DICT_ID
# Unique classname suffixed by this identifier.
default_dict_class_name = 'DefaultDict' + str(_DEFAULT_DICT_ID)
# Increment this identifier to preserve uniqueness.
_DEFAULT_DICT_ID += 1
# Dynamically generated default dictionary class specific to this callable.
default_dict_class = type(
default_dict_class_name, (dict,), {'__missing__': keygen,})
# Instantiate and return the first and only instance of this class.
return default_dict_class()
_DEFAULT_DICT_ID = 0
'''
Unique arbitrary identifier with which to uniquify the classname of the next
:func:`DefaultDict`-derived type.
'''
La clé ... l'obtenir, clé ? à cette magie arcanique est l'appel à la variante à 3 paramètres de la fonction intégrée type()
:
type(default_dict_class_name, (dict,), {'__missing__': keygen,})
Cette ligne unique génère dynamiquement une nouvelle sous-classe dict
aliasant la méthode facultative __missing__
À l'appelable défini par l'appelant. Notez le manque évident de passe-partout, réduisant l'utilisation de DefaultDict
à une seule ligne de Python.
Automatisation pour la victoire flagrante.
Je suis d'accord que cela devrait être facile à faire, et également facile à configurer avec différentes valeurs par défaut ou fonctions qui transforment une valeur manquante d'une manière ou d'une autre.
Inspiré par Cecil Curry 's answer , je me suis demandé: pourquoi ne pas avoir le générateur par défaut (soit une constante soit un appelable) comme membre de la classe, au lieu de générer des classes différentes tout le temps? Permettez-moi de démontrer:
# default behaviour: return missing keys unchanged
dic = FlexDict()
dic['a'] = 'one a'
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'
# regardless of default: easy initialisation with existing dictionary
existing_dic = {'a' : 'one a'}
dic = FlexDict(existing_dic)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'
# using constant as default for missing values
dic = FlexDict(existing_dic, default = 10)
print(dic['a'])
# 'one a'
print(dic['b'])
# 10
# use callable as default for missing values
dic = FlexDict(existing_dic, default = lambda missing_key: missing_key * 2)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'bb'
print(dic[2])
# 4
Comment ça marche? Pas si difficile:
class FlexDict(dict):
'''Subclass of dictionary which returns a default for missing keys.
This default can either be a constant, or a callable accepting the missing key.
If "default" is not given (or None), each missing key will be returned unchanged.'''
def __init__(self, content = None, default = None):
if content is None:
super().__init__()
else:
super().__init__(content)
if default is None:
default = lambda missing_key: missing_key
self.default = default # sets self._default
@property
def default(self):
return self._default
@default.setter
def default(self, val):
if callable(val):
self._default = val
else: # constant value
self._default = lambda missing_key: val
def __missing__(self, x):
return self.default(x)
Bien sûr, on peut se demander si l'on veut autoriser la modification de la fonction par défaut après l'initialisation, mais cela signifie simplement supprimer @default.setter
et absorbant sa logique dans __init__
.
L'activation de l'introspection dans la valeur par défaut actuelle (constante) pourrait être ajoutée avec deux lignes supplémentaires.
Sous-classe dict
's __getitem__
méthode. Par exemple, Comment sous-classer correctement dict et remplacer __getitem__ & __setitem __