8

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
4

3 に答える 3

13

@ 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 と互換性があるように、コードを少し更新しました。さらに、スライスすると、親と同じタイプの新しいオブジェクトが作成されます。より良いものにするために、このレシピに穴を開け続けてください. これは実際に手元に置いておくとかなりいいもののように思えます...

于 2012-11-06T21:07:19.593 に答える
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__

于 2012-11-06T20:53:41.833 に答える
1

これが標準リストではできないことはほぼ確実です。

これを行うには、独自のクラスを作成するのが最もクリーンな方法だと思います (おそらく から継承しますlist)。

于 2012-11-06T20:52:57.900 に答える