2009-06-09 7 views
4

Je souhaite créer une classe DataSet qui est essentiellement une liste d'échantillons. Mais j'ai besoin de remplacer chaque opération d'insertion au DataSet.Liste de sous-classes

Existe-t-il un moyen simple de le faire sans écrire mes propres append, extend, iadd, etc.?

MISE À JOUR: Je souhaite ajouter un backpointer à chaque échantillon, en conservant l'index de l'échantillon dans le DataSet. Ceci est nécessaire à l'algorithme de traitement que j'utilise. J'ai une solution, mais elle semble unelegant - une fonction de renumber() - elle s'assure que les backpointers sont valides.

+0

Comme Alex a répondu, il semble difficile. Mais peut-être que les gens peuvent trouver une solution élégante si vous nous expliquez ce que devrait faire l'insertion substituée. – NicDumZ

+0

Je pense qu'il fait référence au patch-singe. – Geo

+0

@NicDumZ: J'ai mis à jour la question –

Répondre

5

Je ne connais pas une façon de faire ce que vous demandez - remplacer les mutateurs sans les surcharger. Avec un décorateur de classe, cependant, vous pouvez "automatiser" les versions dominantes (en supposant que chacune peut être obtenue en enveloppant la méthode correspondante dans la classe de base), donc ce n'est pas trop ...

Supposons par exemple que ce que vous vouloir faire est ajouter un drapeau "modifié", vrai si les données peuvent avoir été modifiées depuis le dernier appel à .save (une de vos méthodes qui persistent les données et définit self.modified à False).

Alors ...:

def wrapMethod(cls, n): 
    f = getattr(cls, n) 
    def wrap(self, *a): 
     self.dirty = True 
     return f(self, *a) 
    return wrap 

def wrapListMutators(cls): 
    for n in '''__setitem__ __delitem__ __iadd__ __imul__ 
       append extend insert pop remove reverse sort'''.split(): 
    f = wrapMethod(cls, n) 
    setattr(cls, n, f) 
    return cls 

@wrapListMutators 
class DataSet(list): 
    dirty = False 
    def save(self): self.dirty = False 

Cette syntaxe nécessite Python 2.6 ou mieux, mais, dans les versions antérieures Python (ceux qui ne supportent que les décorateurs sur def déclarations, et non sur class déclarations, ou même très vieux qui ne supportent pas les décorateurs du tout), il vous suffit de changer la dernière partie (la déclaration class), à:

class DataSet(list): 
    dirty = False 
    def save(self): self.dirty = False 
DataSet = wrapListMutators(DataSet) 

OIEau, la syntaxe décorateur propre est juste une petite quantité de sucre de syntaxe au-dessus d'un appel de fonction normale qui prend la classe comme argument et la réaffecte.

Edit: maintenant que vous avez modifié votre question pour clarifier vos besoins exacts - maintenir sur chaque élément un champ bp tel que, pour tous i, theset[i].bp == i - il est plus facile de peser les avantages et les inconvénients des différentes approches.

Vous pouvez adapter l'approche que je dessinais, mais au lieu de self.dirty affectation avant l'appel à la méthode enveloppée, ont un appel self.renumber() après, à savoir:

def wrapMethod(cls, n): 
    f = getattr(cls, n) 
    def wrap(self, *a): 
     temp = f(self, *a) 
     self.renumber() 
     return temp 
    return wrap 

cela répond à vos exigences énoncées, mais dans de nombreux cas, il fera beaucoup plus de travail que nécessaire: par exemple, lorsque vous append un élément, cela renumérote inutilement tous les existants (aux mêmes valeurs qu'ils avaient déjà). Mais comment une approche entièrement automatisée peut-elle "savoir" quels éléments, le cas échéant, elle doit recalculer le .bp de, sans effort O(N)? Au moins, il doit regarder chacun d'entre eux (puisque vous ne voulez pas coder séparément, par exemple, append vs insert & c), et c'est déjà O(N).

Cela ne sera donc acceptable que si toutes les modifications apportées à la liste sont acceptées O(N) (essentiellement si la liste reste toujours petite et/ou ne change pas souvent).

Une idée plus fructueuse pourrait être de ne pas maintenir .bp valeurs tout le temps, mais seulement "juste à temps" lorsque cela est nécessaire.Rendre bp une propriété (en lecture seule), en appelant une méthode qui vérifie si le conteneur est "sale" (où le drapeau "sale" dans le conteneur est maintenu en utilisant le code automatisé que j'ai déjà donné) et seulement renomme le conteneur (et définit son attribut "dirty" à False). Cela fonctionnera bien quand la liste est généralement soumise à une rafale de changements et seulement alors vous devez accéder aux éléments 'bp pendant un moment, puis un autre groupe de changements, etc. Une telle alternance entre changement et lecture n'est pas rare dans les conteneurs du monde réel, mais vous seul pouvez savoir si cela s'applique dans votre cas particulier!

Pour obtenir des performances au-delà de cela, je pense que vous devez faire un codage manuel en plus de cette approche générale pour tirer parti des cas spéciaux fréquents. Par exemple, append peut être appelé très souvent, et la quantité de travail à faire dans un cas particulier append est vraiment faible, il peut donc être utile d'écrire ces deux ou trois lignes de code (ne pas définir le bit sale pour ce cas). Un inconvénient: aucune approche ne fonctionnera (en effet votre exigence devient contradictoire) si un élément est présent deux fois dans la liste - ce qui bien sûr est parfaitement possible, sauf si vous prenez des précautions pour l'éviter (vous pouvez facilement le diagnostiquer dans renumber - en conservant un ensemble d'éléments déjà vus et en levant une exception sur toute duplication - si ce n'est pas trop tard pour vous, il est plus difficile de diagnostiquer "à la volée", c'est à dire lors d'une mutation qui provoque une duplication, si c'est ce dont vous avez besoin). Peut-être que vous pouvez assouplir votre exigence de sorte que, si un article est présent deux fois, c'est OK et le bp peut simplement indiquer l'un des indices; ou faire bp dans un définir des indices où l'élément est présent (cela offrirait également une approche en douceur pour le cas d'obtenir bp à partir d'un élément qui est pas dans la liste). Etc; Je vous recommande de considérer (et document!) Tous ces cas d'angle en profondeur - l'exactitude avant la performance!

+0

Etes-vous sûr, Alex? Les docs pour UserList disent: "Remarque: Ce module est disponible uniquement pour la compatibilité descendante Si vous écrivez du code qui n'a pas besoin de travailler avec des versions de Python antérieures à Python 2.2, pensez à sous-classer directement le type de liste intégré. " http://www.python.org/doc/2.5.2/lib/module-UserList.html –

+0

@Janet, oui, j'avais mal parlé, alors j'ai édité la réponse, merci. –

+0

De rien! Je viens d'ajouter beaucoup de discussion de votre cas spécifique, donc je vous recommande de relire la réponse dans son état actuel (tx pour l'accepter même dans la forme incomplète précédente tho ;-). Si vous avez besoin de beaucoup plus de discussion ou de code, je vous recommande d'ouvrir une nouvelle question, car c'est de loin la plus longue réponse SO que j'ai jamais donnée (tx pour poser un problème aussi intéressant!). –