列出回调函数?

9
有没有办法让list在修改时调用一个函数?
例如:
>>>l = [1, 2, 3]
>>>def callback():
       print "list changed"
>>>apply_callback(l, callback)  # Possible?
>>>l.append(4)
list changed
>>>l[0] = 5
list changed
>>>l.pop(0)
list changed
5

我的原始答案也缺少了 __iadd____imul__ :). 我刚刚添加了它们。 - mgilson
3个回答

14

参考 @sr2222 的建议,这是我的尝试。(我将使用一个没有语法糖的装饰器):

import sys

_pyversion = sys.version_info[0]

def callback_method(func):
    def notify(self,*args,**kwargs):
        for _,callback in self._callbacks:
            callback()
        return func(self,*args,**kwargs)
    return notify

class NotifyList(list):
    extend = callback_method(list.extend)
    append = callback_method(list.append)
    remove = callback_method(list.remove)
    pop = callback_method(list.pop)
    __delitem__ = callback_method(list.__delitem__)
    __setitem__ = callback_method(list.__setitem__)
    __iadd__ = callback_method(list.__iadd__)
    __imul__ = callback_method(list.__imul__)

    #Take care to return a new NotifyList if we slice it.
    if _pyversion < 3:
        __setslice__ = callback_method(list.__setslice__)
        __delslice__ = callback_method(list.__delslice__)
        def __getslice__(self,*args):
            return self.__class__(list.__getslice__(self,*args))

    def __getitem__(self,item):
        if isinstance(item,slice):
            return self.__class__(list.__getitem__(self,item))
        else:
            return list.__getitem__(self,item)

    def __init__(self,*args):
        list.__init__(self,*args)
        self._callbacks = []
        self._callback_cntr = 0

    def register_callback(self,cb):
        self._callbacks.append((self._callback_cntr,cb))
        self._callback_cntr += 1
        return self._callback_cntr - 1

    def unregister_callback(self,cbid):
        for idx,(i,cb) in enumerate(self._callbacks):
            if i == cbid:
                self._callbacks.pop(idx)
                return cb
        else:
            return None


if __name__ == '__main__':
    A = NotifyList(range(10))
    def cb():
        print ("Modify!")

    #register a callback
    cbid = A.register_callback(cb)

    A.append('Foo')
    A += [1,2,3]
    A *= 3
    A[1:2] = [5]
    del A[1:2]

    #Add another callback.  They'll be called in order (oldest first)
    def cb2():
        print ("Modify2")
    A.register_callback(cb2)
    print ("-"*80)
    A[5] = 'baz'
    print ("-"*80)

    #unregister the first callback
    A.unregister_callback(cbid)

    A[5] = 'qux'
    print ("-"*80)

    print (A)
    print (type(A[1:3]))
    print (type(A[1:3:2]))
    print (type(A[5]))
这个好处在于如果你发现自己忘记考虑了某种特定的方法,只需添加1行代码即可。 (例如,我直到现在才想起了__iadd__和__imul__ :)) 编辑 我稍微更新了一下代码,使其兼容py2k和py3k。此外,切片创建与父对象相同类型的新对象。请继续挑毛病,这样我就可以让它变得更好。这似乎是一个非常不错的东西...

如果你想让这个程序不仅仅是玩具,而是更有用的话,你的回调函数需要能够意识到列表及其中正在发生的变化。 - Silas Ray
@sr2222 -- 可能。不过让它意识到列表很容易:A.add_callback( lambda:callback(A) )。我想,如果不改变框架,让它知道正在发生什么变化可能会有点棘手。 - mgilson
2
在2.x中还有__setslice____delslice__ - Eryk Sun
你可能还想重写__getslice__(在3.x中为slice对象重写__getitem__)以返回一个NotifyList - Eryk Sun
@eryksun -- 这是一个合理的建议,__setslice____delslice__ 的调用也很正确。我原本以为这些方法会被 __setitem____delitem__ 处理(通常是这样),但你指出了这一点。 - mgilson
显示剩余6条评论

4
你需要子类化 `list` 并修改 `__setitem__`。
class NotifyingList(list):

    def __init__(self, *args, **kwargs):
        self.on_change_callbacks = []

    def __setitem__(self, index, value):
        for callback in self.on_change_callbacks:
            callback(self, index, value)
        super(NotifyingList, self).__setitem__(name, index)

notifying_list = NotifyingList()

def print_change(list_, index, value):
    print 'Changing index %d to %s' % (index, value)

notifying_list.on_change_callbacks.append(print_change)

正如评论中所提到的,这不仅仅涉及到__setitem__

您甚至可以通过构建一个实现list接口并在自身中动态添加和删除描述符的对象来更好地服务于您,而不是使用普通的列表机制。然后,您可以将回调调用减少到仅包括描述符的__get____set____delete__


3
__delitem__popremoveinsertappendextendsortreverse...这是很多工作,但至少添加它们是一件相当琐碎的事情。 - mgilson
@eryksun -- 我也在 Python 列表方法列表中看到了它们 :) - mgilson
也许只需要修改__getattribute__函数,加上所有相关函数的列表... ;) - unddoch
1
@pythonm:仅仅获取属性并不意味着你已经调用了方法。 - Eryk Sun
我指的是类似这样的代码:if attr in self.relevantgmethods: callback();return list.__getattribute__(self, attr) - unddoch

1
我几乎可以确定这不能用标准列表完成。
我认为最干净的方法是编写自己的类来完成这个任务(可能继承自list)。

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