En tant qu'exercice, et surtout pour mon propre amusement, j'implémente un analyseur de paquets de retour en arrière. L'inspiration pour cela est que j'aimerais avoir une meilleure idée de la façon dont les macros hygiéniques fonctionneraient dans un langage de type algol (comme associé aux dialectes lisp sans syntaxe dans lesquels vous les trouvez normalement). Pour cette raison, différentes passes dans l'entrée peuvent voir des grammaires différentes, donc les résultats d'analyse mis en cache ne sont pas valides, sauf si je stocke également la version actuelle de la grammaire avec les résultats d'analyse mis en cache. ( EDIT : une conséquence de cette utilisation des collections clé-valeur est qu'elles devraient être immuables, mais je n'ai pas l'intention d'exposer l'interface pour leur permettre d'être modifiées, donc les collections mutables ou immuables conviennent)
Le problème est que les dictionnaires python ne peuvent pas apparaître comme des clés d'autres dictionnaires. Même utiliser un tuple (comme je le ferais de toute façon) n'aide pas.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Je suppose que ce doit être des tuples tout en bas. Maintenant, la bibliothèque standard python fournit approximativement ce dont j'aurais besoin, collections.namedtuple
a une syntaxe très différente, mais peut être utilisée comme clé. suite de la session ci-dessus:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
D'accord. Mais je dois créer une classe pour chaque combinaison possible de clés dans la règle que je voudrais utiliser, ce qui n'est pas si mal, car chaque règle d'analyse sait exactement quels paramètres elle utilise, afin que cette classe puisse être définie en même temps comme fonction qui analyse la règle.
Edit: Un problème supplémentaire avec les namedtuple
s est qu'ils sont strictement positionnels. Deux tuples qui semblent devoir être différents peuvent en fait être identiques:
>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False
tl'dr: Comment obtenir des dict
s qui peuvent être utilisés comme clés pour d'autres dict
s?
Après avoir piraté un peu les réponses, voici la solution la plus complète que j'utilise. Notez que cela fait un peu plus de travail pour rendre les dictats résultants vaguement immuables à des fins pratiques. Bien sûr, il est toujours assez facile de pirater en appelant, dict.__setitem__(instance, key, value)
mais nous sommes tous des adultes ici.
class hashdict(dict):
"""
hashable dict implementation, suitable for use as a key into
other dicts.
>>> h1 = hashdict({"apples": 1, "bananas":2})
>>> h2 = hashdict({"bananas": 3, "mangoes": 5})
>>> h1+h2
hashdict(apples=1, bananas=3, mangoes=5)
>>> d1 = {}
>>> d1[h1] = "salad"
>>> d1[h1]
'salad'
>>> d1[h2]
Traceback (most recent call last):
...
KeyError: hashdict(bananas=3, mangoes=5)
based on answers from
http://stackoverflow.com/questions/1151658/python-hashable-dicts
"""
def __key(self):
return tuple(sorted(self.items()))
def __repr__(self):
return "{0}({1})".format(self.__class__.__name__,
", ".join("{0}={1}".format(
str(i[0]),repr(i[1])) for i in self.__key()))
def __hash__(self):
return hash(self.__key())
def __setitem__(self, key, value):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def __delitem__(self, key):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def clear(self):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def pop(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def popitem(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def setdefault(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def update(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
# update is not ok because it mutates the object
# __add__ is ok because it creates a new object
# while the new object is under construction, it's ok to mutate it
def __add__(self, right):
result = hashdict(self)
dict.update(result, right)
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
hashdict
doit être immuable, au moins après avoir commencé à le hacher, alors pourquoi ne pas mettre en cache les valeurskey
et enhash
tant qu'attributs duhashdict
objet? J'ai modifié__key()
et__hash__()
, et testé pour confirmer qu'il est beaucoup plus rapide. SO n'autorise pas le code formaté dans les commentaires, je vais donc le lier ici: sam.aiki.info/hashdict.py