Come funziona la ricetta del contatore ordinato

Leggendo come funziona super() , ho trovato questa ricetta su come creare un contatore ordinato:

 from collections import Counter, OrderedDict class OrderedCounter(Counter, OrderedDict): 'Counter that remembers the order elements are first seen' def __repr__(self): return '%s(%r)' % (self.__class__.__name__, OrderedDict(self)) def __reduce__(self): return self.__class__, (OrderedDict(self),) 

Per esempio:

 oc = OrderedCounter('adddddbracadabra') print(oc) OrderedCounter(OrderedDict([('a', 5), ('d', 6), ('b', 2), ('r', 2), ('c', 1)])) 

Qualcuno è in grado di spiegare come funziona magicamente?

Questo appare anche nella documentazione di Python .

OrderedCounter viene fornito come esempio nella documentazione OrderedDict e funziona senza la necessità di sovrascrivere alcun metodo:

 class OrderedCounter(Counter, OrderedDict): pass 

Quando viene chiamato un metodo di class, Python deve trovare il metodo corretto da eseguire. Esiste un ordine definito in cui viene eseguita la ricerca nella gerarchia di classi chiamata “metodo resolution order” o mro. Il mro è memorizzato nell’attributo __mro__ :

 OrderedCounter.__mro__ (, , , , ) 

Quando un’istanza di OrderedDict chiama __setitem__() , ricerca le classi nell’ordine: OrderedCounter , Counter , OrderedDict (dove è stato trovato). Quindi un’istruzione come oc['a'] = 0 finisce per chiamare OrderedDict.__setitem__() .

Al contrario, __getitem__ non è sovrascritto da nessuna delle sottoclassi del mro, quindi count = oc['a'] è gestito da dict.__getitem__() .

 oc = OrderedCounter() oc['a'] = 1 # this call uses OrderedDict.__setitem__ count = oc['a'] # this call uses dict.__getitem__ 

Una sequenza di chiamate più interessante si verifica per un’istruzione come oc.update('foobar'). Innanzitutto viene chiamato Counter.update() . Il codice per Counter.update() usa self [elem], che diventa una chiamata a OrderedDict.__setitem__() . E il codice per questo chiama dict.__setitem__() .

Se le classi base sono invertite, non funziona più. Perché il mro è diverso e vengono chiamati i metodi sbagliati.

 class OrderedCounter(OrderedDict, Counter): # <<<== doesn't work pass 

Maggiori informazioni su mro possono essere trovate nella documentazione di Python 2.3.