如何检测嵌套列表中的元素已更改?(Python)

4
我有一个大的二维列表(列表的列表),每个元素包含一个由整数、字符串和字典组成的列表。我希望能够确定任何被修改的元素的“路径”(例如[2] [3] [2] ["items"] [2]最差!),并在修改时触发此操作。这个列表太大了,无法扫描并查看已更改的内容!理想情况下,我还想要一个新元素的副本,尽管这可以稍后找到。
我的第一次尝试是创建一个类,并覆盖其__setattr__方法:
class Notify():
    def __setattr__(self, name, value):
        self.__dict__[name] = value       #Actually assign the value
        print name, value                 #This will tell us when it fires

然而,__setattr__方法仅在设置未通过索引(或键)访问的变量时触发,因为这似乎将调用外部的list()/dict()类而不是我们的类。
>>> test = Notify()
>>> test.var = 1          #This is detected
var 1

>>> test.var = [1,2,3]    #Now let's set it to a list
var [1, 2, 3]             

>>> test.var[0] = 12      #But when we assign a value via an index, it doesn't fire 
>>> test.var
[12, 2, 3]                #However it still assigns the value, so it must be talking to the list itself!

因此,总结一下,我希望有一种方法可以告诉我发生了什么更改(索引/键列表),并且需要在发生更改时立即发生,因为扫描整个列表太昂贵了。我也不能依赖修改列表的代码提供详细信息。如果这在第n个嵌套列表中不可能实现,那么我可以使用只给出前两个索引的内容,因为那里的数据不会太大以至于无法扫描。非常感谢您的帮助!编辑:仍然没有成功,尽管这个问题Track changes to lists and dictionaries in python?似乎接近我所需要的。不幸的是,我不太擅长类,并需要会类的人的帮助!编辑:看了这个Python: Right way to extend list让我想到继承list可能是一个坏主意。我使用代理类来编写了以下代码。但是,原始问题仍然存在,即对嵌套列表的修改不记录。类组合而不是继承是一个好主意吗?
from UserList import UserList

class NotifyList(UserList):

    def __init__(self, initlist=None):
        self.data = []
        if initlist is not None:
            if type(initlist) is list:
                self.data[:] = initlist
            elif isinstance(initlist, NotifyList):
                self.data[:] = initlist.data[:]
            else:
                self.data = list(initlist)

    def __setitem__(self, key, item):
        if type(item) is list:
            self.data[key] = NotifyList(item)
        else:
            self.data[key] = item
        print key, item

    def append(self, item):
        if type(item) is list:
            self.data.append(NotifyList(item))
        else:
            self.data.append(item)
        print self.index(item), item

1
也许你可以在 __setattr__ 中检查值的类型,如果它是一个列表,你可以将它包装在一个 Notify 对象中? - lelloman
也许这个答案可以帮助你。 - Mauro Baraldi
@lelloman 我对Python中的类还不太熟悉,但如果新建一个Notify类用于新对象,它仍然会像那些对象一样(例如对列表使用.append())吗? - Daniel Spencer
我认为@MauroBaraldi指出的答案是你所需要的,但如果你不熟悉Python中的面向对象编程,那么它并不容易理解。 - lelloman
还是没有成功的答案,有没有实际可行的解决方案? - Daniel Spencer
显示剩余4条评论
2个回答

1
你需要在可追踪的列表中创建一个报告链,其中每个列表都会向其父级报告修改。在你的NotifyList类中,构造函数中添加一个父级参数和一个用于父级识别新项目的ID参数。当父级是列表时,这将是列表索引:
class NotifyList(UserList):
    def __init__(self, inilist=None, parent=None, id=None):
        self.parent = parent
        self.id = id
        # remainder of __init__()...

当发生修改时,应该通知父级。例如在__setitem__中:
def __setitem__(self, key, item):
    if type(item) is list:
        self.data[key] = NotifyList(item, self, str(key)) # Don't forget the new item's parent
    else:
        self.data[key] = item
    self.alertParent(str(key), str(item)) # Report up the chain instead of printing

alertParent() is:

def alertParent(self, key, item):
    strChange = "[{0}] = {1}".format(key, item)
    self.parent.notifyChange(self.id, strChange)

notifyChange() 是如何工作的?

def notifyChange(self, childKey, strChangeInChild):
    strChange = "[{0}]{1}".format(childKey, strChangeInChild)
    self.parent.notifyChange(self.id, strChange)

它只是将通知沿着链路传播,并在消息中添加自己的ID。
唯一缺失的环节是,报告链的顶部会发生什么?变更消息应该最终被打印出来。以下是一个简单的技巧,通过重用alertParent()来实现:
def alertParent(self, key, item):
    if self.parent is None: # I am the root
        print "[{0}]{1}".format(key, item)
    else:
        # remainder of alertParent() shown above...
...
def notifyChange(self, childKey, strChangeInChild):
    if self.parent is None: # I am the root
        self.alertParent(childKey, strChangeInChild) # Actually just prints a change msg
    else:
        # remainder of notifyChange() shown above...

我编写了这段代码,完整版可以在这里 [Google Doc] 获取(相对于我上面展示的内容,有一些微不足道的错误修复)。演示如下:

>>> from test import NotifyList
>>> top = NotifyList([0]*3, None, None) # Now we have [0 0 0]
>>> print top
NList-[0, 0, 0]

>>> top[0] = NotifyList([0]*3, top, 0) # Now we have [ [0 0 0]  0  0 ]
[0] = NList-[0, 0, 0] #-------------- The tracking msg is fired

>>> print top
NList-[<test.NotifyList object at 0x0000000002163320>, 0, 0]

>>> top[0][1] = NotifyList([0]*3, top[0], 1) # Now we have [ [[0 0 0] 0 0]  0  0 ]
[0][1] = NList-[0, 0, 0] #----------- The tracking msg fired again

>>> top[0][1][2] = "this is a string" # Now we have [ [[0 0 "this is a string] 0 0]  0  0 ]
[0][1][2] = this is a string #------- And another tracking msg

>>> print top[0][1][2]
this is a string

>>> print top[0][1]
NList-[0, 0, 'this is a string']

0
我的 jsonfile 模块可以检测到(嵌套的)JSON兼容的Python对象的更改。只需继承 JSONFileRoot 类,即可根据您的需求适应更改检测。
>>> import jsonfile
>>> class Notify(jsonfile.JSONFileRoot):
...   def on_change(self):
...     print(f'notify: {self.data}')
... 
>>> test = Notify()
>>> test.data = 1
notify: 1
>>> test.data = [1,2,3]
notify: [1, 2, 3]
>>> test.data[0] = 12
notify: [12, 2, 3]
>>> test.data[1] = {"a":"b"}
notify: [12, {'a': 'b'}, 3]
>>> test.data[1]["a"] = 20
notify: [12, {'a': 20}, 3]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接