8
votes

Liste des rappels?

Y a-t-il un moyen de faire une liste code> appeler une fonction à chaque fois que la liste est modifiée?

Par exemple: P>

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


1 commentaires

Ma réponse originale était également manquante __ iadd __ et __ imul __ :). Je viens de les ajouter.


3 Réponses :


1
votes

Je suis presque certain que cela ne peut pas être fait avec la liste standard.

Je pense que le moyen le plus propre serait d'écrire votre propre classe pour le faire (peut-être hériter de la liste ).


0 commentaires

4
votes

Vous auriez à la sous-classe list et modifier __ settitem __ . xxx

comme indiqué dans les commentaires, c'est plus que juste __ settitem __ .

Vous pourriez même être mieux servi en construisant un objet qui implémente la liste interface et ajoute de manière dynamique les descripteurs à la place de la Machines de liste normales. Ensuite, vous pouvez réduire vos appels de rappel à seulement le descripteur __ obtenez __ , __ ensemble __ et __ Supprimer __ .


5 commentaires

et __ delitem __ et pop et supprimer et insérez et appendez et Étendre et Trier et inverse ... c'est beaucoup de travail, mais au moins c'est une chose assez triviale à ajouter ...


@ Syeryksun - Je viens de voir ceux de la liste des méthodes de liste Python aussi :)


Peut-être simplement modifier __ getattribute __ avec une liste de toutes les fonctions concernées ...;)


@pythonm: Il suffit de recevoir l'attribut que vous avez appelé la méthode.


Je voulais dire quelque chose comme si attr dans self.relevantgmethods: rappel (); liste de retour .__ getattribute __ (auto, attr)



13
votes

Emprunter à partir de la suggestion de @ SR2222, voici ma tentative. (Je vais utiliser un décorateur sans le sucre syntaxique):

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


11 commentaires

Les rappels devraient s'attendre à soi-même, à l'opération et aux données pertinentes pour l'opération, je pense. Sinon, très gentil.


@ SR2222 - Je pense que les rappels ne devraient s'attendre à rien, bien qu'il soit suffisamment facile de changer de recommandation (à gauche en tant qu'exercice pour le lecteur curieux). Cependant, la fourniture d'informations supplémentaires à un rappel est généralement accomplie via des fermetures et lambda . En effet, je pense que c'est une raison pour laquelle lambda est donc important dans la langue. C'est comme ça que c'est fait dans tkinter par exemple.


Je pense que je pense que vous voulez que cela soit utile comme plus qu'un jouet, vos rappels veulent pouvoir être au courant de la liste et les choses qui y avaient changé.


@ SR2222 - Peut-être. Cela signifie que la liste est vraiment facile, cependant: A.ADD_Callback (Lambda: rappel (a)) . Je suppose que l'avoir conscient de ce qui est changeant est un peu plus difficile sans changer le cadre.


Dans 2.x, il y a aussi __ setslice __ et __ delslice __ .


En fait, pas vraiment, maintenant que j'y pense. Il suffit de faire rappel (auto, func, * args, ** kwargs) (ou peut-être func .__ Nom __ ) et votre callback sait tout.


Vous voudrez peut-être aussi remplacer __ getlice __ (et __ getItem __ in 3.x, pour tranche objets) pour renvoyer un notifique .


@ Syeryksun - C'est une suggestion raisonnable et bon appel sur __ setslice __ et __ delslice __ . Je pensais que ceux-ci seraient géré par __ settitem __ et __ delitem __ (ils sont généralement) ...


Python 2.x a des ops spécifiques de bytecode pour la tranchée. Les méthodes de séquence associées sont obsolètes mais toujours utilisées par des types intégrés. Voici la fonction assign_slice dans CEval. C . Il essaie d'abord d'utiliser la méthode de séquence sq_ass_slice (méthode de liste list_ass_slice ), alors qu'il construit un tranche un objet.


@ Syeryksun - J'ai mis à jour avec vos suggestions. J'ai testé (brièvement) avec PY2K et PY3K. N'hésitez pas à éditer / commenter si vous avez d'autres suggestions.


@ Syeryksun - L'existence des diverses méthodes __ semble plus stupide par la seconde. Quoi qu'il en soit, j'ai mis à jour, a ajouté quelques tests supplémentaires qui semblent bien. Aussi, j'ai ajouté un moyen de désinscrire les rappels qui avaient été enregistrés auparavant - il m'a plus ou moins forcé de changer la liste de rappel à une dicte (ce que je ne suis pas terriblement heureux de).