2
votes

Définition des valeurs par défaut dans une classe

Je crée une classe en Python et je ne sais pas comment définir correctement les valeurs par défaut. Mon objectif est de définir des valeurs par défaut pour toutes les instances de classe, qui peuvent également être modifiées par une méthode de classe. Cependant, j'aimerais que les valeurs par défaut initiales soient restaurées après avoir appelé une méthode.

J'ai pu la faire fonctionner avec le code ci-dessous. Ce n'est pas très "joli", donc je pense que ce sont de meilleures approches à ce problème.

class plots:
    def __init__(self, **kwargs):
        self.default_attr = {'a': 1, 'b': 2, 'c': 3}
        self.default_attr.update(kwargs)
        self.__dict__.update((k, v) for k, v in self.default_attr.items())

    def method1(self, **kwargs):
        self.__dict__.update((k, v) for k, v in kwargs.items())

        #### Code for this method goes here

        # Then restore initial default values
        self.__dict__.update((k, v) for k, v in self.default_attr.items())

Lorsque j'utilise cette classe, je ferais quelque chose comme my_instance = plots () et my_instance.method1 () , my_instance.method1 (b = 5) et my_instance.method1 () code >. Lors de l'appel de method1 la troisième fois, b serait 5 si je ne réinitialise pas les valeurs par défaut à la fin de la définition de la méthode, mais j'aimerais qu'il soit 2 encore.

Remarque: le code ci-dessus n'est qu'un exemple. La classe réelle a des dizaines de valeurs par défaut, et les utiliser toutes comme arguments d'entrée serait considérée comme un anti-modèle.

Des suggestions sur la façon de résoudre correctement ce problème?


7 commentaires

def __init __ (self, a = 1, b = 2, c = 3, ** kwargs): ? Quant à method1 , il semble que ses kwargs aient une portée locale, alors pourquoi se donner la peine de définir des variables globales puis de les réinitialiser à la fin? Utilisez simplement les kwargs en tant que locaux dans method1


@Dan Je pense que l'attente est que le code de la méthode appelle d'autres méthodes, et il veut qu'elles voient les attributs temporairement modifiés.


La bibliothèque mock fournit patch.dict , que vous pouvez utiliser comme gestionnaire de contexte.


eh bien, si le désordre est le problème, alors self .__ dict __. update ((k, v) pour k, v dans kwargs.items ()) -> self .__ dict __. update (kwargs)


Je suis surpris par votre suggestion @chepner, est-ce une pratique acceptable / acceptée d'utiliser des outils de test dans ce contexte?


@ReblochonMasque C'est une question délicate. patch.dict , puisqu'il met à jour les données plutôt que de patcher un nom, semble au moins un peu déplacé dans la bibliothèque mock . Mais utiliser mock dans le code de production laisserait un mauvais goût dans ma bouche, d'où le commentaire plutôt qu'une réponse.


Noté @chepner, merci de votre réponse.


3 Réponses :


1
votes

Vous pouvez utiliser des variables de classe et une propriété pour atteindre votre objectif de définir des valeurs par défaut pour toutes les instances de classe. Les valeurs des instances peuvent être modifiées directement, et les valeurs par défaut initiales restaurées après l'appel d'une méthode.

Compte tenu du contexte selon lequel "la classe réelle a des dizaines de valeurs par défaut", une autre approche que vous pouvez envisager consiste à configurer un fichier de configuration contenant les valeurs par défaut, et en utilisant ce fichier pour initialiser ou réinitialiser les valeurs par défaut.

Voici un court exemple de la première approche utilisant une variable de classe: p>

1
42
1

résultat:

class Plots:

    _a = 1

    def __init__(self):
        self._a = None
        self.reset_default_values()

    def reset_default_values(self):
        self._a = Plots._a

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = value


plot = Plots()
print(plot.a)

plot.a = 42
print(plot.a)

plot.reset_default_values()
print(plot.a)


0 commentaires

0
votes

Vous pouvez utiliser un gestionnaire de contexte ou un décorateur pour appliquer et réinitialiser les valeurs sans avoir à taper le même code sur chaque méthode.

Plutôt que d'avoir self.default_attr , je voudrais juste revenir à l'état précédent.

En utilisant un décorateur, vous pourriez obtenir:

class Transparent:
    pass


def with_kwargs(fn):
    def inner(self, **kwargs):
        new_self = Transparent()
        new_self.__dict__ = {**self.__dict__, **kwargs}
        return fn(new_self)
    return inner

À mon humble avis, c'est une mauvaise idée, et je suggérerais au moins ne pas muter les tracés . Vous pouvez le faire en créant un nouvel objet et en le passant à method1 comme self.

def with_kwargs(fn):
    def inner(self, **kwargs):
        prev = self.__dict__.copy()
        try:
            self.__dict__.update(kwargs)
            ret = fn(self)
        finally:
            self.__dict__ = prev
        return ret
    return inner


class plots:
    a = 1
    b = 2
    c = 3

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    @with_kwargs
    def method1(self):
        # Code goes here


0 commentaires

0
votes

Il existe de nombreuses façons de résoudre ce problème, mais si vous avez installé python 3.7 (ou avez 3.6 et installez le backport ), dataclasses pourrait être une bonne convient pour une bonne solution.

Tout d'abord, il vous permet de définir les valeurs par défaut de manière lisible et compacte, et permet également toutes les opérations de mutation dont vous avez besoin:

>>> from dataclasses import dataclass, MISSING
>>> @dataclass
... class Plots:
...     a: int = 1
...     b: int = 2
...     c: int = 3
... 
...     def reset(self):
...         for name, field in self.__dataclass_fields__.items():
...             if field.default != MISSING:
...                 setattr(self, name, field.default)
...             else:
...                 setattr(self, name, field.default_factory())
...
>>> p = Plots(a=-1)     # create a Plot with some non-default values  
>>> p
Plots(a=-1, b=2, c=3)
>>> p.reset()           # calling reset on it restores the pre-defined defaults
>>> p
Plots(a=1, b=2, c=3)

Vous avez également la possibilité de définir gratuitement des usines par défaut au lieu des valeurs par défaut avec le définition de champ de classe de données . Ce n'est peut-être pas encore un problème, mais cela évite la valeur par défaut modifiable gotcha , que chaque programmeur python rencontre tôt ou tard.

Dernier point mais non le moindre, écrire une fonction reset est assez facile étant donné une classe de données existante, car elle conserve trace de toutes les valeurs par défaut déjà dans son attribut __dataclass_fields__ :

>>> from dataclasses import dataclass
>>> @dataclass
... class Plots:
...     a: int = 1
...     b: int = 2
...     c: int = 3
...     
>>> p = Plots()        # create a Plot with only default values
>>> p
Plots(a=1, b=2, c=3)
>>> p.a = -1           # update something in this Plot instance
>>> p
Plots(a=-1, b=2, c=3)

Vous pouvez maintenant écrire une fonction do_stuff (...) qui met à jour les champs dans une instance de Plot, et tant que vous exécutez reset () les modifications ne seront pas conservées.


0 commentaires