2012-03-26 11 views
9

En resumen: ¿cuál es la forma rápida de comprobar si una enorme lista en python ha cambiado? hashlib necesita un búfer, y construir una representación de cadena de esa lista es inviable.Comprueba si la enorme lista en python ha cambiado

En long: Tengo una ENORME lista de diccionarios que representan datos. Ejecuto varios análisis sobre estos datos, pero hay algunos aspectos de los metadatos que requieren todos los análisis, es decir. el conjunto de temas (cada dict en la lista tiene una clave de tema, y ​​en ocasiones solo necesito una lista de todos los sujetos que tienen datos presentes en el conjunto de datos). Así que me gustaría poner en práctica lo siguiente:

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 pregunta es cómo poner en práctica el método has_changed, o más específicamente, get_hash (cada objeto ya tiene un método __hash__, pero por defecto, simplemente devuelve el objeto de id , que no cambia cuando, por ejemplo, agregamos un elemento a una lista).

+1

¿Cómo es tu método 'change_data'? También 'self.subjects' se puede construir como' self.subjects = set (datum ['subject'] para datum en self.data) '. – eumiro

+0

Creo que es posible que necesite dar más detalles. ¿Tienes versiones antiguas y nuevas? ¿Puedes usar frozendicts? ¿Importa el orden? ¿Tu código está creando los cambios? – Marcin

+5

¿Puede simplemente tener una variable de instancia 'has_changed' que establezca siempre que cambie' data'? De lo contrario, probablemente necesites un objeto proxy para delegar todo, excepto 'has_changed' en los' datos 'reales. – agf

Respuesta

7

Un enfoque más sofisticado sería trabajar con elementos de datos proxy en lugar de listas nativas y diccionarios, lo que podría marcar cualquier cambio en sus atributos. Para hacerlo más flexible, incluso podría codificar una devolución de llamada para ser utilizada en caso de cualquier cambio.

Entonces, suponiendo que solo tiene que ocuparse de listas y diccionarios en su estructura de datos, podemos trabajar con clases heredadas de dict y listas con una devolución de llamada cuando se accede a cualquier método de cambio de datos en el objeto. La lista completa de métodos es en 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 actualización

uno hace vivir y aprender: listas nativas puede ser cambiado por métodos nativos por las llamadas que pasan por alto los métodos mágicos. El sistema a prueba de tontos es el mismo enfoque, pero hereda de collections.abc.MutableSequence en su lugar, y mantiene una lista nativa como un atributo interno de su objeto proxy.

+1

De corazón segundo este enfoque. Si detectar cambios después de que el hecho sea demasiado costoso (lo que su descripción indica que es definitivamente el caso), simplemente realice un seguimiento de los cambios a medida que ocurren. La razón para usar una huella digital hash como lo intentó originalmente Nkosinathi es si necesita mantener múltiples versiones en caché y necesita una forma única de identificarlas. Si todo lo que hace es detectar cambios, este enfoque es mucho más adecuado. –

2

Usted puede conseguir fácilmente la representación de cadena de un objeto utilizando la biblioteca de la salmuera, y luego pasarlo a hashlib, como usted ha dicho:

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)) 

Entonces, eso es una manera, no sé si es el más rápido manera. Funcionará para objetos arbitrarios. Pero, como dijo Agf, en su caso sería más eficiente si pudiera usar una variable has_changed que modifique cada vez que modifique datos.

+0

Eso funciona de hecho, pero es bastante lento. El problema es que no siempre sé si una operación alterará la lista (además de que muchos de los análisis que uso han sido escritos por otras personas, y cambiarlos no es posible). La función ni siquiera tiene que ser completamente precisa, lo hará un 'has_probably_changed' rápido. –

1

hashlib necesita un búfer, y la construcción de una cadena de representación de esa lista es inviable.

Puede update de hash en muchos pasos:

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

Por lo tanto, no es necesario convertir toda la lista a una representación de cadena. Simplemente itere sobre él, convirtiendo a cadena solo un elemento y llamando al update.

Cuestiones relacionadas