Qual è la differenza tra la soluzione che utilizza defaultdict e quella che utilizza setdefault?

1

In Think Python l'autore introduce defaultdict . Quello che segue è un estratto dal libro riguardante defaultdict :

If you are making a dictionary of lists, you can often write simpler code using defaultdict. In my solution to Exercise 12-2, which you can get from http://thinkpython2.com/code/anagram_sets.py, I make a dictionary that maps from a sorted string of letters to the list of words that can be spelled with those letters. For example, 'opst' maps to the list ['opts', 'post', 'pots', 'spot', 'stop', 'tops']. Here’s the original code:

 def all_anagrams(filename):
     d = {}
     for line in open(filename):
         word = line.strip().lower()
         t = signature(word)
         if t not in d:
             d[t] = [word]
         else:
             d[t].append(word) return d

This can be simplified using setdefault, which you might have used in Exercise 11-2:

 def all_anagrams(filename):
     d = {}
     for line in open(filename):
         word = line.strip().lower()
         t = signature(word)
         d.setdefault(t, []).append(word) return d

This solution has the drawback that it makes a new list every time, regardless of whether it is needed. For lists, that’s no big deal, but if the factory function is complicated, it might be. We can avoid this problem and simplify the code using a defaultdict:

def all_anagrams(filename):
    d = defaultdict(list)
    for line in open(filename):             
        word = line.strip().lower() 
        t = signature(word)
        d[t].append(word)
    return d

Ecco la definizione della funzione signature :

def signature(s):
    """Returns the signature of this string.

    Signature is a string that contains all of the letters in order.

    s: string
    """
    # TODO: rewrite using sorted()
    t = list(s)
    t.sort()
    t = ''.join(t)
    return t 

Quello che ho capito per quanto riguarda la seconda soluzione è che setdefault controlla se t (la firma della parola) esiste come una chiave, altrimenti, la imposta come una chiave e imposta una lista vuota come valore, quindi append aggiunge la parola ad esso. Se t esiste, setdefault restituisce il suo valore (una lista con almeno un elemento, che è una stringa che rappresenta una parola), e append aggiunge la parola a questo elenco.

Quello che ho capito per quanto riguarda la terza soluzione è che d , che rappresenta un defaultdict , rende t una chiave e imposta una lista vuota come valore (se t non è già esiste come chiave), quindi la parola viene aggiunta all'elenco. Se t esiste già, viene restituito il suo valore (l'elenco) e al quale viene aggiunta la parola.

Qual è la differenza tra la seconda e la terza soluzione? I Cosa significa che il codice nella seconda soluzione crea ogni volta un nuovo elenco, indipendentemente dal fatto che sia necessario? In che modo setdefault è responsabile per questo? In che modo utilizzare defaultdict ci fa evitare questo problema? In che modo la seconda e la terza soluzione sono diverse?

    
posta Mahmud Muhammad Naguib 20.08.2017 - 02:54
fonte

1 risposta

5

La linea chiave è questa:

d.setdefault(t, []).append(word) return d

d.setdefault è solo una funzione. Quella funzione sta ricevendo due argomenti: t, e una nuova lista. Entrambi questi argomenti vengono valutati prima che venga chiamato setdefault, il che significa che la lista è costruita, quindi viene chiamato setdefault, e potrebbe immediatamente scartare il nuovo elenco.

L'implementazione interna di setdefault potrebbe essere simile a:

def setdefault(self, key, default):
    try:
        return self[key]
    except KeyError:
        self[key] = default
        return self[key]

Tieni presente che se key esiste, default non viene mai utilizzato! Nel caso di defaultdict , costruisce un nuovo oggetto solo quando ne hai bisogno. Un'implementazione potrebbe essere simile a:

class defaultdict(dict):

    def __init__(self, factory):
        self.factory = factory

    def __missing__(self, key):
        self[key] = self.factory()
        return self[key]

__missing__ viene chiamato dai dizionari quando un elemento non è nel dizionario. In questo caso, un oggetto viene creato solo in quel caso.

Come sottolinea l'autore, le liste sono relativamente economiche, ma cosa succederebbe se l'oggetto in costruzione aprisse un file? O una presa di rete? Poi hai quel sovraccarico, solo per buttarlo via subito dopo.

Lo svantaggio di defaultdict, ovviamente, è che tutti gli elementi nel tuo dizionario ottengono lo stesso valore iniziale. (Il valore di ritorno di qualunque callable è fornito al costruttore defaultdict.) Ovviamente, puoi sempre usare il precedente come esempio per avere un comportamento diverso se tutto ciò di cui hai bisogno è la chiave.

Si noti che le reali implementazioni di setdefault e defaultdict sono probabilmente molto più complesse - ho sorvolato molti dettagli e ci sono probabilmente miglioramenti delle prestazioni e codice per gestire casi limite che non ho pensato .

    
risposta data 20.08.2017 - 07:46
fonte

Leggi altre domande sui tag