Come creare correttamente sottoclassi e sovrascrivere __getitem__ e __setitem__


84

Sto eseguendo il debug di un codice e voglio scoprire quando si accede a un particolare dizionario. Bene, in realtà è una classe che sottoclasse dicte implementa un paio di funzionalità extra. Comunque, quello che vorrei fare è sottoclasse dictme stesso e aggiungere override __getitem__e __setitem__produrre un output di debug. In questo momento, l'ho fatto

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'è una chiave che alla fine verrà impostata che voglio utilizzare per identificare l'output. Ho quindi cambiato la classe che sto strumentando in sottoclasse DictWatchanziché dicte ho cambiato la chiamata al superconstructor. Tuttavia, sembra che non stia succedendo nulla. Pensavo di essere intelligente, ma mi chiedo se dovrei andare in una direzione diversa.

Grazie per l'aiuto!


Hai provato a utilizzare la stampa anziché il registro? Inoltre, potresti spiegare come crei / configuri il tuo log?
pajton

2
Non ci dict.__init__vuole *args?
Tom Russell

4
Sembra un po 'come un buon candidato per un decoratore.
Tom Russell

Risposte:


39

Quello che stai facendo dovrebbe assolutamente funzionare. Ho testato la tua classe e, a parte una parentesi di apertura mancante nelle tue istruzioni di registro, funziona perfettamente. Ci sono solo due cose a cui riesco a pensare. Innanzitutto, l'output dell'istruzione di log è impostato correttamente? Potrebbe essere necessario inserire un logging.basicConfig(level=logging.DEBUG)all'inizio del copione.

Secondo, __getitem__e __setitem__vengono chiamati solo durante gli []accessi. Quindi assicurati di accedere solo DictWatchtramite d[key], anziché d.get()ed.set()


In realtà non si tratta di parentesi extra, ma di una parentesi di apertura mancante in giro(str(dict.get(self, 'name_label')), str(key), str(val)))
cobbal

3
Vero. All'OP: per riferimento futuro, puoi semplicemente fare log.info ('% s% s% s', a, b, c), invece di un operatore di formattazione di stringhe Python.
BrainCore

Il livello di registrazione è finito per essere il problema. Sto eseguendo il debug del codice di qualcun altro e stavo originariamente testando in un altro file che si trova a un diverso livello di set di debug. Grazie!
Michael Mior

73

Un altro problema durante la sottoclasse dictè che il built-in __init__non chiama updatee il built-in updatenon chiama __setitem__. Quindi, se vuoi che tutte le operazioni setitem passino attraverso la tua __setitem__funzione, dovresti assicurarti che venga chiamata tu stesso:

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).iteritems():
            self[k] = v

9
Se stai usando Python 3, ti consigliamo di cambiare questo esempio in modo che printsia la print()funzione e il update()metodo usa items()invece di iteritems().
Al Sweigart

Ho provato il tuo sol, ma sembra che funzioni solo per un solo livello di indicizzazione (ovvero, dict [key] e non dict [key1] [key2] ...) *
Andrew Naguib

d [key1] restituisce qualcosa, forse un dizionario. La seconda chiave lo indicizza. Questa tecnica non può funzionare a meno che la cosa restituita non supporti anche il comportamento dell'orologio.
Matt Anderson

1
@AndrewNaguib: Perché dovrebbe funzionare con array nidificati? L'array annidato non funziona nemmeno con il normale dict di Python (se non l'hai implementato da solo)
Igor Chubin

1
@AndrewNaguib: __getitem__avrebbe bisogno di testare vale farlo solo in modo condizionale - cioèif isinstance(val, dict): ...
martineau

14

Considera la creazione di sottoclassi UserDicto UserList. Queste classi sono intese come sottoclassi mentre le normali dicte listnon lo sono e contengono ottimizzazioni.


9
Per riferimento, la documentazione in Python 3.6 dice "La necessità di questa classe è stata parzialmente soppiantata dalla capacità di creare sottoclassi direttamente da dict; tuttavia, questa classe può essere più facile da lavorare perché il dizionario sottostante è accessibile come un attributo".
Sean

@andrew un esempio potrebbe essere utile.
Vasantha Ganesh


9

Ciò non dovrebbe davvero cambiare il risultato (che dovrebbe funzionare, per buoni valori di soglia di registrazione): il tuo init dovrebbe essere:

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

invece, perché se chiami il tuo metodo con DictWatch ([(1,2), (2,3)]) o DictWatch (a = 1, b = 2) questo fallirà.

(o, meglio, non definire un costruttore per questo)


Sono preoccupato solo per la dict[key]forma di accesso, quindi questo non è un problema.
Michael Mior

1

Tutto quello che dovrai fare è

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Un esempio di utilizzo per uso personale

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Nota : testato solo in python3


0

Per completare la risposta di Andrew Pate, ecco un esempio che mostra una differenza tra dicte UserDict:

È difficile sovrascrivere dict correttamente:

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}

UserDictereditano da collections.abc.MutableMapping, quindi è molto più facile personalizzare:

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}

Allo stesso modo, è sufficiente implementare __getitem__per essere automaticamente compatibile con key in my_dict, my_dict.get, ...

Nota: UserDictnon è una sottoclasse di dict, quindi isinstance(UserDict(), dict)fallirà (ma isinstance(UserDict(), collections.abc.MutableMapping)funzionerà)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.