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
@ 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 と互換性があるように、コードを少し更新しました。さらに、スライスすると、親と同じタイプの新しいオブジェクトが作成されます。より良いものにするために、このレシピに穴を開け続けてください. これは実際に手元に置いておくとかなりいいもののように思えます...
サブクラス化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__
。
これが標準リストではできないことはほぼ確実です。
これを行うには、独自のクラスを作成するのが最もクリーンな方法だと思います (おそらく から継承しますlist
)。