2012-03-26 79 views
9

簡而言之:如何檢查python中的巨大列表是否發生了變化? hashlib需要一個緩衝區,並且構建該列表的字符串表示形式是不可行的。檢查python中的巨大列表是否已更改

總之:我有一大堆代表數據的字典。我對這些數據進行了大量的分析,但是所有分析都需要一些元數據方面的信息。一組主題(列表中的每個詞典都有一個主題關鍵詞,有時我只需要列出數據集中存在數據的所有主題)。所以我想實現如下:

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 

的問題是如何實現has_changed方法,或者更具體地說,get_hash(每個對象已有一個__hash__方法,但默認情況下它只是返回對象的id ,當我們例如將一個元素附加到列表中時,它不會改變)。

+1

你的'change_data'方法是怎麼樣的?此外'self.subjects'可以建立爲'self.subjects = set(datum ['subject']爲datum in self.data)''。 – eumiro 2012-03-26 11:24:37

+0

我想你可能需要提供一些更多的細節。你有新舊版本嗎?你可以使用frozendicts?訂單是否重要?您的代碼是否創建了更改? – Marcin 2012-03-26 11:29:12

+5

只要你改變'data',你能設置一個'has_changed'實例變量嗎?否則,你可能需要一個代理對象來委託所有的東西,但將'has_changed'委託給真正的'data'。 – agf 2012-03-26 11:35:52

回答

7

更復雜的方法是使用代理數據元素而不是本地列表和字典,這可能會標記對其屬性的任何更改。爲了使其更加靈活,您甚至可以編寫回調以便在發生任何更改時使用。

因此,假設您只需處理數據結構中的列表和字典 - 當訪問對象上的任何數據更改方法時,我們可以使用從dict繼承的類和帶回調的列表完整的方法列表是在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年更新

一個人在生活和學習:本地列表可以通過本地方法通過繞過魔術方法的調用改變。傻瓜證明系統是相同的方法,但是繼承collections.abc.MutableSequence而不是,保留本地列表作爲您的代理對象的內部屬性。

+1

我衷心地第二種這種方法。如果在事實過於昂貴(您的描述顯示確實如此)後檢測到更改,則只需在發生變化時跟蹤更改。使用哈希指紋作爲Nkosinathi最初嘗試的原因是,如果您需要保留多個版本的緩存並需要一種唯一標識它們的方法。如果你只是在檢測變化,這種方法更適合。 – 2012-03-28 19:18:12

2

您可以方便地使用泡菜庫中的任何對象的字符串表示,然後把它傳遞給hashlib,如你所說:

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

所以,這是一個方式,我不知道如果是最快的的方式。它將適用於任意對象。但是,正如agf所說,就你而言,如果你可以使用每次實際修改數據時修改的變量has_changed,肯定會更有效率。

+0

確實有效,但速度很慢。問題是,我並不總是知道手術是否會改變名單(加上我使用的許多分析都是其他人寫的,而改變它們都是不可行的)。該函數甚至不必完全準確,快速'has_probably_changed'將會執行。 – 2012-03-26 11:59:01

1

hashlib需要一個緩衝區,並且構建該列表的字符串表示形式是不可行的。

可以update散在許多步驟:

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

所以,你並不需要所有的列表轉換爲字符串表示。您只需遍歷它,將字符串轉換爲只有一個項目,然後調用update