2012-03-26 16 views
9

In breve: qual è il modo a digiuno per controllare se una lista enorme in python è cambiata? hashlib necessita di un buffer e la creazione di una rappresentazione di stringa di tale elenco non è fattibile.Controlla se l'enorme lista in python è cambiata

A lungo: ho una lista ENORME di dizionari che rappresentano i dati. Eseguo una serie di analisi su questi dati, ma ci sono alcuni aspetti dei metadati che sono richiesti da tutte le analisi, es. l'insieme di soggetti (ogni dict nella lista ha una chiave soggetto, e a volte ho solo bisogno di un elenco di tutti i soggetti che hanno dati presenti nel set di dati.). Così mi piacerebbe implementare la seguente:

class Data: 
    def __init__(self, ...): 
     self.data = [{...}, {...}, ...] # long ass list of dicts 
     self.subjects = set() 
     self.hash = 0 

    def get_subjects(self): 
     # recalculate set of subjects only if necessary 
     if self.has_changed(): 
      set(datum['subject'] for datum in self.data) 

     return self.subjects 

    def has_changed(self): 
     # calculate hash of self.data 
     hash = self.data.get_hash() # HOW TO DO THIS? 
     changed = self.hash == hash 
     self.hash = hash # reset last remembered hash 
     return changed 

La domanda è come implementare il metodo has_changed, o più specificamente, get_hash (ogni oggetto ha già un metodo di __hash__, ma di default restituisce solo l'oggetto del id , che non cambia quando ad esempio aggiungiamo un elemento ad una lista).

+1

Come si presenta il metodo 'change_data'? Anche 'self.subjects' può essere costruito come' self.subjects = set (datum ['subject'] per datum in self.data) '. – eumiro

+0

Penso che potrebbe essere necessario fornire ulteriori dettagli. Hai entrambe le versioni vecchie e nuove? Puoi usare frozendicts? L'ordine è importante? Il tuo codice sta creando le modifiche? – Marcin

+5

Puoi semplicemente avere una variabile di istanza 'has_changed' che imposti ogni volta che cambi i' dati'? Altrimenti, probabilmente hai bisogno di un oggetto proxy per delegare tutto tranne 'has_changed' ai veri' dati'. – agf

risposta

7

Un approccio più sofisticato sarebbe quello di lavorare con gli elementi di dati proxy invece di liste e dizionari nativi, che potrebbero segnalare qualsiasi modifica ai loro attributi. Per renderlo più flessibile, potresti persino codificare un callback da utilizzare in caso di eventuali modifiche.

Quindi, supponendo che tu abbia a che fare solo con elenchi e dizionari sulla struttura dei dati - possiamo lavorare con classi che ereditano da dict e list con un callback quando si accede a qualsiasi metodo di modifica dei dati sull'oggetto L'elenco completo dei metodi è in http://docs.python.org/reference/datamodel.html

# -*- coding: utf-8 -*- 
# String for doctests and example: 
""" 
      >>> a = NotifierList() 
      >>> flag.has_changed 
      False 
      >>> a.append(NotifierDict()) 
      >>> flag.has_changed 
      True 
      >>> flag.clear() 
      >>> flag.has_changed 
      False 
      >>> a[0]["status"]="new" 
      >>> flag.has_changed 
      True 
      >>> 

""" 


changer_methods = set("__setitem__ __setslice__ __delitem__ update append extend add insert pop popitem remove setdefault __iadd__".split()) 


def callback_getter(obj): 
    def callback(name): 
     obj.has_changed = True 
    return callback 

def proxy_decorator(func, callback): 
    def wrapper(*args, **kw): 
     callback(func.__name__) 
     return func(*args, **kw) 
    wrapper.__name__ = func.__name__ 
    return wrapper 

def proxy_class_factory(cls, obj): 
    new_dct = cls.__dict__.copy() 
    for key, value in new_dct.items(): 
     if key in changer_methods: 
      new_dct[key] = proxy_decorator(value, callback_getter(obj)) 
    return type("proxy_"+ cls.__name__, (cls,), new_dct) 


class Flag(object): 
    def __init__(self): 
     self.clear() 
    def clear(self): 
     self.has_changed = False 

flag = Flag() 

NotifierList = proxy_class_factory(list, flag) 
NotifierDict = proxy_class_factory(dict, flag) 

2017 aggiornamento

uno fa vivere e imparare: liste native può essere cambiato metodi nativi per le chiamate che ignorano i metodi magici. Il sistema infallibile è lo stesso approccio, ma ereditando da collections.abc.MutableSequence invece, e mantenendo un elenco nativo come un attributo interno del tuo oggetto proxy.

+1

In secondo luogo, seguo questo approccio. Se rilevi le modifiche dopo che il fatto è troppo costoso (che la tua descrizione indica che è sicuramente il caso), ti basta monitorare le modifiche mentre accadono. Il motivo per utilizzare un'impronta digitale hash come Nkosinathi inizialmente provato è se è necessario mantenere più versioni memorizzate nella cache e necessitare di un modo per identificarle in modo univoco. Se tutto quello che stai facendo è rilevare i cambiamenti, questo approccio è molto più adatto. –

2

Si può facilmente ottenere la rappresentazione di stringa di qualsiasi oggetto utilizzando la libreria salamoia, e poi passarlo a hashlib, come lei ha detto:

import pickle 
import hashlib 

data = [] 
for i in xrange(100000): 
    data.append({i:i}) 

print hashlib.md5(pickle.dumps(data)) 

data[0] = {0:1} 
print hashlib.md5(pickle.dumps(data)) 

Quindi, questo è un modo, non lo so se è il modo più veloce . Funzionerà per oggetti arbitrari. Ma, come detto, nel tuo caso sarebbe sicuramente più efficiente se fosse possibile utilizzare una variabile has_changed modificata ogni volta che si modifica effettivamente i dati.

+0

Funziona davvero, ma è piuttosto lento. Il problema è che non sempre so se un'operazione altererà la lista (più molte delle analisi che uso sono state scritte da altre persone, e cambiarle tutte non è fattibile). La funzione non ha nemmeno bisogno di essere completamente accurata, farà un rapido 'has_probably_changed'. –

1

hashlib ha bisogno di un buffer e la creazione di una stringa di rappresentazione di tale elenco non è fattibile.

È possibile update hash in molti passaggi:

>>> import hashlib 
>>> m = hashlib.md5() 
>>> m.update("Nobody inspects") 
>>> m.update(" the spammish repetition") 

Quindi, non c'è bisogno di convertire tutta la lista ad una rappresentazione di stringa. Basta iterare su di esso, convertendo in stringa solo un elemento e chiamando update.

Problemi correlati