Comment créer une sous-classe de dict aussi «parfaite» que possible? L'objectif final est d'avoir un dict simple dans lequel les touches sont en minuscules.
Il semblerait qu'il devrait y avoir un petit ensemble de primitives que je peux remplacer pour que cela fonctionne, mais selon toutes mes recherches et tentatives, il semble que ce ne soit pas le cas:
Si je remplace
__getitem__
/__setitem__
, alorsget
/set
ne fonctionne pas. Comment puis-je les faire fonctionner? Je n'ai sûrement pas besoin de les mettre en œuvre individuellement?Suis-je empêcher le décapage de fonctionner, et dois-je mettre en œuvre
__setstate__
etc?Ai-je besoin
repr
,update
et__init__
?Dois-je simplement utiliser mutablemapping (il semble que l'on ne devrait pas utiliser
UserDict
ouDictMixin
)? Si c'est le cas, comment? Les documents ne sont pas vraiment instructifs.
Voici mon premier essai, get()
cela ne fonctionne pas et il y a sans aucun doute de nombreux autres problèmes mineurs:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
la source
Réponses:
Vous pouvez écrire un objet qui se comporte
dict
assez facilement avec les ABC (classes de base abstraites) ducollections.abc
module. Il vous indique même si vous avez manqué une méthode, voici donc la version minimale qui ferme l'ABC.Vous obtenez quelques méthodes gratuites de l'ABC:
Je ne sous-
dict
classerais pas (ou d'autres modules intégrés) directement. Cela n'a souvent aucun sens, car ce que vous voulez réellement faire, c'est implémenter l'interface de adict
. Et c'est exactement à cela que servent les ABC.la source
__keytransform__()
car il viole le guide de style PEP 8 qui conseille "Ne jamais inventer de tels noms; utilisez-les uniquement comme documenté" à la fin de la section Descriptive: Naming Styles .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Ne vérifiez pas le type d'un objet, vérifiez l'interface.La réponse acceptée serait ma première approche, mais comme il y a des problèmes et que personne n'a abordé l'alternative, en fait la sous-classe a
dict
, je vais le faire ici.Quel est le problème avec la réponse acceptée?
Cela me semble être une demande assez simple:
La réponse acceptée ne fait pas réellement partie de la sous
dict
- classe , et un test pour cela échoue:Idéalement, tout code de vérification de type testerait l'interface que nous attendons, ou une classe de base abstraite, mais si nos objets de données sont passés à des fonctions qui testent
dict
- et nous ne pouvons pas "corriger" ces fonctions, ce code échouera.D'autres problèmes que l'on pourrait faire:
fromkeys
.La réponse acceptée a également une redondance
__dict__
- prenant donc plus de place en mémoire:Sous-classement réel
dict
Nous pouvons réutiliser les méthodes dict par héritage. Tout ce que nous devons faire est de créer une couche d'interface qui garantit que les clés sont passées dans le dict en minuscules s'il s'agit de chaînes.
Eh bien, les mettre en œuvre individuellement est l'inconvénient de cette approche et l'avantage de l'utiliser
MutableMapping
(voir la réponse acceptée), mais ce n'est vraiment pas beaucoup plus de travail.Tout d'abord, factorisons la différence entre Python 2 et 3, créons un singleton (
_RaiseKeyError
) pour nous assurer que nous savons si nous obtenons réellement un argumentdict.pop
et créons une fonction pour nous assurer que nos clés de chaîne sont en minuscules:Maintenant, nous implémentons - j'utilise
super
avec les arguments complets pour que ce code fonctionne pour Python 2 et 3:Nous utilisons une approche presque plaque de chaudière pour toute méthode ou méthode spéciale qui fait référence à une clé, mais autrement, par héritage, nous obtenons des méthodes:
len
,clear
,items
,keys
,popitem
etvalues
gratuitement. Bien que cela ait nécessité une réflexion approfondie pour bien faire les choses, il est trivial de voir que cela fonctionne.(Notez que cet
haskey
élément a été déconseillé dans Python 2, supprimé dans Python 3.)Voici quelques utilisations:
décapage
Et les cornichons de sous-classe dict très bien:
__repr__
Nous avons défini
update
et__init__
, mais vous avez une belle__repr__
par défaut:Cependant, il est bon d'écrire un
__repr__
pour améliorer la débogabilité de votre code. Le test idéal esteval(repr(obj)) == obj
. Si c'est facile à faire pour votre code, je le recommande fortement:Vous voyez, c'est exactement ce dont nous avons besoin pour recréer un objet équivalent - c'est quelque chose qui pourrait apparaître dans nos journaux ou dans les backtraces:
Conclusion
Oui, ce sont quelques lignes de code supplémentaires, mais elles sont destinées à être complètes. Ma première inclination serait d'utiliser la réponse acceptée, et s'il y avait des problèmes avec elle, je regarderais alors ma réponse - car c'est un peu plus compliqué, et il n'y a pas d'ABC pour m'aider à obtenir mon interface correcte.
L'optimisation prématurée va vers une plus grande complexité à la recherche de performances.
MutableMapping
est plus simple - il obtient donc un avantage immédiat, toutes choses étant égales par ailleurs. Néanmoins, pour exposer toutes les différences, comparons et contrastons.Je dois ajouter qu'il y a eu une pression pour mettre un dictionnaire similaire dans le
collections
module, mais il a été rejeté . Vous devriez probablement le faire à la place:Il devrait être beaucoup plus facile à déboguer.
Compare et nuance
Il y a 6 fonctions d'interface implémentées avec
MutableMapping
(ce qui manquefromkeys
) et 11 avec ladict
sous - classe. Je ne ai pas besoin de mettre en œuvre__iter__
ou__len__
, mais je dois mettre en œuvreget
,setdefault
,pop
,update
,copy
,__contains__
etfromkeys
- mais ceux - ci sont assez trivial, puisque je peux utiliser l' héritage pour la plupart de ces implémentations.L'
MutableMapping
implémente certaines choses en Python quidict
implémentent en C - donc je m'attends à ce qu'unedict
sous - classe soit plus performante dans certains cas.Nous obtenons une gratuité
__eq__
dans les deux approches - qui n'assument l'égalité que si un autre dict est tout en minuscules - mais encore une fois, je pense que ladict
sous - classe se comparera plus rapidement.Résumé:
MutableMapping
- classement est plus simple avec moins de possibilités de bogues, mais plus lent, prend plus de mémoire (voir dict redondant) et échoueisinstance(x, dict)
dict
- classement est plus rapide, utilise moins de mémoire et passeisinstance(x, dict)
, mais sa mise en œuvre est plus complexe.Quel est le plus parfait? Cela dépend de votre définition de parfait.
la source
__slots__
ou peut-être de réutiliser le__dict__
comme magasin, mais cela mélange la sémantique, un autre point de critique potentiel.ensure_lower
sur le premier argument (qui est toujours la clé)? Ce serait alors le même nombre de remplacements, mais ils seraient tous de la forme__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- je pense que cela devrait le faire, non? Je pense qu'il devrait tester l'interface - par exemple, l'objet DataFrame pandas n'est pas une instance de Mapping (au dernier contrôle) mais il a des éléments / iteritems.Mes exigences étaient un peu plus strictes:
Ma pensée initiale était de substituer notre classe Path maladroite à une sous-classe unicode insensible à la casse - mais:
some_dict[CIstr(path)]
laid)J'ai donc finalement dû écrire ce dict insensible à la casse. Merci au code de @AaronHall qui a été rendu 10 fois plus facile.
Implicite vs explicite est toujours un problème, mais une fois la poussière retombée, renommer les attributs / variables pour commencer par ci (et un gros commentaire de doc expliquant que ci signifie insensible à la casse), je pense que c'est une solution parfaite - comme les lecteurs du code doivent sachez que nous avons affaire à des structures de données sous-jacentes insensibles à la casse. J'espère que cela corrigera certains bogues difficiles à reproduire, qui, je suppose, se résument à la sensibilité à la casse.
Commentaires / corrections bienvenus :)
la source
__repr__
devraient utiliser les classes parentes__repr__
pour passer le test eval (repr (obj)) == obj (je ne pense pas que ce soit le cas actuellement) et ne pas se fier à__str__
.total_ordering
décorateur de classe - qui éliminera 4 méthodes de votre sous-classe unicode. Mais la sous-classe dict semble très intelligemment mise en œuvre. : PCIstr.__repr__
, dans votre cas, peut passer le test de repr avec très peu de tracas, et cela devrait rendre le débogage beaucoup plus agréable. J'ajouterais également un__repr__
pour votre dict. Je vais le faire dans ma réponse pour le démontrer.__slots__
dans CIstr - cela fait une différence dans les performances (CIstr n'est pas censé être sous-classé ni utilisé en dehors de LowerDict, devrait être une classe finale imbriquée statique). Toujours pas sûr comment résoudre le problème de manière élégante rééd (la piqûre peut contenir une combinaison'
et"
citations)Tout ce que vous aurez à faire, c'est
OU
Un exemple d'utilisation pour mon usage personnel
Remarque : testé uniquement en python3
la source
Après avoir essayé les deux premiers deux suggestions, je me suis installé sur une route moyenne louche à la recherche de Python 2.7. Peut-être que 3 est plus sain, mais pour moi:
que je déteste vraiment, mais qui semble correspondre à mes besoins, à savoir:
**my_dict
dict
, cela contourne votre code . Essaye le.isinstance(my_dict, dict)
dict
Si vous devez vous distinguer des autres, personnellement, j'utilise quelque chose comme ça (bien que je recommande de meilleurs noms):
Tant que vous n'avez besoin que de vous reconnaître en interne, de cette façon, il est plus difficile d'appeler accidentellement en
__am_i_me
raison du munging de nom de python (cela est renommé_MyDict__am_i_me
de tout ce qui appelle en dehors de cette classe). Un peu plus privé que_method
s, à la fois dans la pratique et sur le plan culturel.Jusqu'à présent, je n'ai rien à redire, à part la
__class__
dérogation à l' ombre sérieuse . Je serais ravi d'entendre parler des problèmes que d'autres rencontrent avec cela, je ne comprends pas complètement les conséquences. Mais jusqu'à présent, je n'ai eu aucun problème, ce qui m'a permis de migrer beaucoup de code de qualité intermédiaire dans de nombreux endroits sans avoir besoin de changements.Comme preuve: https://repl.it/repls/TraumaticToughCockatoo
Fondamentalement: copiez l'option # 2 actuelle , ajoutez des
print 'method_name'
lignes à chaque méthode, puis essayez ceci et regardez la sortie:Vous verrez un comportement similaire pour d'autres scénarios. Disons que votre faux
dict
est un wrapper autour d'un autre type de données, il n'y a donc pas de moyen raisonnable de stocker les données dans le dicté;**your_dict
sera vide, quelles que soient les autres méthodes.Cela fonctionne correctement pour
MutableMapping
, mais dès que vous en héritezdict
devient incontrôlable.Edit: en tant que mise à jour, cela fonctionne sans problème depuis près de deux ans maintenant, sur plusieurs centaines de milliers (eh, peut-être quelques millions) de lignes de python complexes héritées. Je suis donc assez content avec ça :)
Edit 2: apparemment, j'ai mal copié ceci ou quelque chose il y a longtemps.
@classmethod __class__
ne fonctionne pas pour lesisinstance
contrôles -@property __class__
fonctionne: https://repl.it/repls/UnitedScientificSequencela source
**your_dict
sera vide» (si vous sous-classe dedict
)? Je n'ai vu aucun problème avec le déballage de dict ...**your_dict
cela n'exécute pas votre code, donc il ne peut rien produire de "spécial". Par exemple, vous ne pouvez pas compter les «lectures» car il n'exécute pas votre code de lecture. MutableMapping fait le travail pour cela (utiliser si vous le pouvez!), Mais il neisinstance(..., dict)
donc je ne pouvais pas l' utiliser. yay logiciel hérité.**your_dict
, mais je trouve très intéressant queMutableMapping
cela fasse cela.**some_dict
est assez courant. À tout le moins , il arrive très souvent dans les décorateurs, donc si vous avez une , vous êtes immédiatement au risque de mauvaise conduite en apparence impossible si vous ne prennent pas en compte pour elle.def __class__()
astuce ne semble pas fonctionner avec Python 2 ou 3, au moins pour l'exemple de code dans la question Comment enregistrer l'implémentation d'abc.MutableMapping en tant que sous-classe dict?(modifié pour fonctionner autrement dans les deux versions). Je veuxisinstance(SpreadSheet(), dict)
revenirTrue
.