2017-05-31 1 views
1

Je suis un peu nouveau avec python et j'ai eu la tâche de créer une classe "UndoList" (liste de type) avec une méthode undo(). Cette méthode doit annuler typiques liste-opérations comme, append, insérer, supprimer ...Comment implémenter un undo() - Méthode pour une classe List en Python

>>> ul = UndoList([1,2,3]) 
>>> ul.append(4), print(ul), undo(ul), print(ul) 
[1,2,3,4] 
[1,2,3] 
>>> ul.remove(3), print(ul), undo(ul), print(ul) 
[1,2] 
[1,2,3] 
... 

Cette undo() - méthode ne doit annuler une opération (comme u peut voir dans l'exemple). Mon professeur m'a donné l'indice, pour sauvegarder la valeur de la liste dans l'instance avant chaque opération.

Ceci est ma classe:

class UndoList(list): 

    def __init__(self, lis): 
     list.__init__(self, lis) 
     self.lis = [] 

    def __append__(self, lis): 
     list.__add__(self, lis) 
     return lis 

    def undo(self): 
     return self 

a1 = UndoList([1,2,3]) 
print(a1), a1.append(4), print(a1) #[1,2,3] [1,2,3,4] 
a1.undo(), print(a1)     #[1,2,3,4] 

Alors maintenant ma question: comment puis-je créer une instance dans ma classe pour sauver ma liste réelle avant que je ne importe quelle opération? Et est-il possible de simplement retruner cette instance dans ma méthode d'annulation?

Merci!

+0

Alors, voulez-vous que nous résolvions votre exercice de classe pour vous ??? – yorodm

+1

vous devez implémenter une sorte d'attribut 'history'. – MSeifert

+0

Voulez-vous dire que vous avez seulement besoin de supporter un seul niveau d'annulation? –

Répondre

3

Voici un code qui vous aidera à démarrer. Vraiment cependant, il est préférable d'éviter de sous-classer les types standard de Python parce que pour le faire correctement, vous devez généralement surcharger chaque méthode, ce qui peut être plutôt fastidieux et sujet aux erreurs. Notez que la méthode append est appelée append, et non __append__. :) Et que les méthodes qui muent une liste in situ renvoient None, pas la liste.

from copy import deepcopy 

class UndoList(list): 
    def __init__(self, *args): 
     super().__init__(*args) 
     self.old = [] 

    def append(self, item): 
     self.old = deepcopy(self[:]) 
     super().append(item) 

    def extend(self, items): 
     self.old = deepcopy(self[:]) 
     super().extend(items) 

    def undo(self): 
     temp = deepcopy(self[:]) 
     self[:] = self.old 
     self.old = temp 


a = UndoList([1, 2, 3]) 
print(a) 

a.append(4) 
print(a) 
a.undo() 
print(a) 
a.undo() 
print(a) 

a.extend([5, 6]) 
print(a) 
a.undo() 
print(a) 

sortie

[1, 2, 3] 
[1, 2, 3, 4] 
[1, 2, 3] 
[1, 2, 3, 4] 
[1, 2, 3, 4, 5, 6] 
[1, 2, 3, 4] 

Nous utilisons def __init__(self, *args) afin que nous puissions appeler UndoList() sans args pour obtenir une UndoList vide.

Comme 9000 mentions dans les commentaires, vous n'avez probablement pas besoin de deepcopy ici. Il consomme de la RAM supplémentaire en copiant récursivement chaque élément de la liste (sauf pour les éléments immuables), et c'est lent. L'utilisation de deepcopy rend UndoList robuste. OTOH, cela signifie également que les éléments restaurés à partir de .old sont des copies des éléments d'origine, et dans certains cas, ce n'est pas souhaitable - si d'autres objets se réfèrent à ces éléments, le processus de sauvegarde rompt cette connexion.

Si vous voulez expérimenter avec cela, il suffit de changer le code qui sauvegarde la liste

self.old = self[:] 

et la méthode undo devient

def undo(self): 
    self[:], self.old = self.old, self[:] 

La façon saine de le faire est de construire une nouvelle classe en utilisant Abstract Base Classes plutôt que de sous-classer list.

+0

Ceci est sans aucun doute une solution de travail, mais un _very_ beaucoup de mémoire. Les chances sont que l'OP n'a pas demandé de protéger les données mutables dans la liste des changements. – 9000

+0

Cela fonctionne très bien! Merci beaucoup, mon pote. Je vais maintenant essayer de construire ceci en utilisant les classes de base abstraites. Merci beaucoup! –

+1

@ 9000 Peut-être 'deepcopy' est trop puissant, et je suis d'accord que ce n'est pas vraiment nécessaire pour' append' ou 'extend', mais il est nécessaire pour' remove', puisque les éléments supprimés pourraient être mutés par un autre code avant un 'undo 'call, et le' undo' ne restaure pas la liste à son état précédent. OTOH, avoir des éléments de liste mutés par un autre code est toujours une possibilité lorsque l'on travaille avec des listes ... –

0

C'est simple. mais fastidieux: vous ajoutez un attribut history à l'objet liste. C'est une pile d'états variables précédents. Chaque opération de modification doit placer son état actuel sur history avant la modification. L'opération undo apparaît simplement la plus récente.

Vous avez déjà noté que vous devrez redéfinir toutes les opérations de modification (telles que __append__ dans votre classe).