2 votes

Suivre les changements d'attributs dans l'instance. Python

Je veux implémenter une fonction qui prend comme argument un objet quelconque et qui suit les changements de valeur d'un attribut spécifique. Ensuite, elle sauvegarde l'ancienne valeur de l'attribut dans l'attribut old_name.

Par exemple :

class MyObject(object):
     attr_one = None
     attr_two = 1

Nommons ma fonction magique magic_function()

Plus que je ne peux faire comme ça :

obj = MyObject()
obj = magic_function(obj)
obj.attr_one = 'new value'
obj.attr_two = 2

et il sauvegarde les anciennes valeurs pour que je puisse obtenir comme ceci

print obj.old_attr_one
None
print obj.attr_one 'new value'

et

print obj.old_attr_two
1
print obj.attr_two
2

Quelque chose comme ça Je me demande comment je peux faire cela sans toucher à la classe de l'instance ?

4voto

Ben Points 22160

C'est un début :

class MagicWrapper(object):
    def __init__(self, wrapped):
        self._wrapped = wrapped

    def __getattr__(self, attr):
        return getattr(self._wrapped, attr)

    def __setattr__(self, attr, val):
        if attr == '_wrapped':
            super(MagicWrapper, self).__setattr__('_wrapped', val)
        else:
            setattr(self._wrapped, 'old_' + attr, getattr(self._wrapped, attr))
            setattr(self._wrapped, attr, val)

class MyObject(object):
    def __init__(self):
        self.attr_one = None
        self.attr_two = 1

obj = MyObject()
obj = MagicWrapper(obj)
obj.attr_one = 'new value'
obj.attr_two = 2

print obj.old_attr_one
print obj.attr_one
print obj.old_attr_two
print obj.attr_two

Ce n'est pas infaillible lorsque vous essayez d'envelopper des objets bizarres (très peu de choses le sont en Python), mais cela devrait fonctionner pour les classes "normales". Vous pourriez écrire beaucoup plus de code pour vous approcher un peu plus du comportement de l'objet enveloppé, mais il est probablement impossible de le faire parfaitement. La principale chose dont il faut être conscient ici est que de nombreuses méthodes spéciales ne seront pas redirigées vers l'objet enveloppé.

Si vous voulez faire cela sans envelopper obj d'une manière ou d'une autre, ça va devenir désordonné. Voici une option :

def add_old_setattr_to_class(cls):
    def __setattr__(self, attr, val):
        super_setattr = super(self.__class__, self).__setattr__
        if attr.startswith('old_'):
            super_setattr(attr, val)
        else:
            super_setattr('old_' + attr, getattr(self, attr))
            super_setattr(attr, val)
    cls.__setattr__ = __setattr__

class MyObject(object):
    def __init__(self):
        self.attr_one = None
        self.attr_two = 1

obj = MyObject()
add_old_setattr_to_class(obj.__class__)
obj.attr_one = 'new value'
obj.attr_two = 2

print obj.old_attr_one
print obj.attr_one
print obj.old_attr_two
print obj.attr_two

Notez que cette méthode est extrêmement envahissante si vous l'utilisez sur des objets fournis en externe. Elle modifie globalement le classe de l'objet auquel vous appliquez la magie, et pas seulement de cette seule instance. C'est parce que comme plusieurs autres méthodes spéciales, __setattr__ n'est pas recherchée dans le dictionnaire d'attributs de l'instance ; la recherche passe directement à la classe, il n'y a donc aucun moyen de remplacer simplement __setattr__ sur l'instance. Je qualifierais ce genre de code de hack bizarre si je le rencontrais dans la nature (c'est une "astuce" si je l'écris moi-même, bien sûr ;) ).

Cette version peut ou non jouer avec les objets qui jouent déjà des tours à l'aide de la technologie de l'information. __setattr__ y __getattr__ / __getattribute__ . Si vous finissez par modifier la même classe plusieurs fois, je pense que cela fonctionne toujours, mais vous vous retrouvez avec un nombre toujours plus grand de wrapped __setattr__ définitions. Vous devriez probablement essayer d'éviter cela, peut-être en mettant un "drapeau secret" sur la classe et en le vérifiant dans le fichier add_old_setattr_to_class avant de modifier cls . Vous devriez probablement aussi utiliser un préfixe plus improbable que juste old_ puisque vous essayez essentiellement de créer un espace de noms entièrement distinct.

0voto

viraptor Points 12779

Vous pouvez remplacer tous les attributs par des propriétés personnalisées au moment de l'exécution. Mais qu'essayez-vous de réaliser ? Peut-être que migrer vers des types complètement immuables serait un meilleur choix ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X