99 votes

Comment sous-classer correctement dict et surcharger __getitem__ & __setitem__ ?

Je suis en train de déboguer un code et je veux savoir quand un dictionnaire particulier est consulté. En fait, il s'agit d'une classe qui sous-classe dict et met en œuvre quelques fonctionnalités supplémentaires. Quoi qu'il en soit, ce que j'aimerais faire, c'est sous-classer dict et ajoutez une dérogation __getitem__ y __setitem__ pour produire des résultats de débogage. Pour l'instant, j'ai

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

'name_label' est une clé qui sera éventuellement définie et que je souhaite utiliser pour identifier la sortie. J'ai ensuite modifié la classe que j'instrumente pour qu'elle devienne la sous-classe DictWatch au lieu de dict et a modifié l'appel au superconstructeur. Pourtant, rien ne semble se passer. Je pensais être malin, mais je me demande si je ne devrais pas prendre une autre direction.

Merci pour votre aide !

0 votes

Avez-vous essayé d'utiliser print au lieu de log ? Aussi, pourriez-vous expliquer comment vous créez/configurez votre journal ?

3 votes

N'est pas dict.__init__ prendre *args ?

4 votes

Il ressemble un peu à un bon candidat pour un décorateur.

85voto

Matt Anderson Points 7461

Un autre problème se pose lorsque l'on sous-classe dict est que le système intégré de __init__ n'appelle pas update et le système intégré update n'appelle pas __setitem__ . Ainsi, si vous voulez que toutes les opérations setitem passent par votre __setitem__ vous devez vous assurer qu'elle est appelée par vous-même :

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print('GET', key)
        return val

    def __setitem__(self, key, val):
        print('SET', key, val)
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        print('update', args, kwargs)
        for k, v in dict(*args, **kwargs).items():
            self[k] = v

17 votes

Si vous utilisez Python 3, vous devrez modifier cet exemple de manière à ce que print est le print() et la fonction update() La méthode utilise items() au lieu de iteritems() .

0 votes

J'ai essayé votre solution, mais il semble qu'elle ne fonctionne que pour un seul niveau d'indexation (c'est-à-dire dict[key] et non dict[key1][key2] ... )*.

0 votes

D[key1] renvoie quelque chose, peut-être un dictionnaire. La deuxième clé indexe ce dictionnaire. Cette technique ne peut fonctionner que si la chose renvoyée prend également en charge le comportement de surveillance.

43voto

BrainCore Points 1936

Ce que vous faites devrait absolument fonctionner. J'ai testé votre classe et, à part une parenthèse ouvrante manquante dans vos déclarations, elle fonctionne parfaitement. Il n'y a que deux choses auxquelles je peux penser. Tout d'abord, la sortie de votre déclaration de journal est-elle correctement définie ? Vous pourriez avoir besoin de mettre un logging.basicConfig(level=logging.DEBUG) au début de votre script.

Deuxièmement, __getitem__ y __setitem__ ne sont appelées que pendant les [] accès. Veillez donc à n'accéder qu'aux DictWatch via d[key] plutôt que d.get() y d.set()

0 votes

En fait, il ne s'agit pas de paraboles supplémentaires, mais d'une parabole d'ouverture manquante autour de (str(dict.get(self, 'name_label')), str(key), str(val)))

3 votes

C'est vrai. A l'OP : Pour référence future, vous pouvez simplement faire log.info('%s %s %s', a, b, c), au lieu d'un opérateur de formatage de chaîne Python.

0 votes

Le niveau de journalisation s'est avéré être le problème. Je suis en train de déboguer le code de quelqu'un d'autre et je testais à l'origine dans un autre fichier qui avait un niveau de débogage différent. Je vous remercie de votre attention.

23voto

andrew pate Points 54

Envisager une sous-classe UserDict o UserList . Ces classes sont destinées à être sous-classées alors que la classe normale dict y list ne le sont pas et contiennent des optimisations.

9voto

makapuf Points 744

Cela ne devrait pas vraiment changer le résultat (qui devrait fonctionner, pour de bonnes valeurs de seuil d'enregistrement) : votre init devrait être :

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

car si vous appelez votre méthode avec DictWatch([(1,2),(2,3)]) ou DictWatch(a=1,b=2), cela échouera.

(ou, mieux, ne pas définir de constructeur pour cela)

0 votes

Je ne m'inquiète que de la dict[key] d'accès, ce n'est donc pas un problème.

8voto

Conchylicultor Points 984

En tant que Réponse d'Andrew Pate proposé, sous-classement collections.UserDict au lieu de dict est beaucoup moins sujette aux erreurs.

Voici un exemple montrant un problème lorsque l'on hérite de dict naïvement :

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)

d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDict hérite de collections.abc.MutableMapping Cela fonctionne donc comme prévu :

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)

d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

De même, il suffit de mettre en œuvre __getitem__ pour être automatiquement compatible avec key in my_dict , my_dict.get ,

Remarque : UserDict es no une sous-classe de dict Ainsi isinstance(UserDict(), dict) échouera (mais isinstance(UserDict(), collections.abc.MutableMapping) fonctionnera).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X