111 votes

Bonne approche pour valider les attributs d'une instance de classe

Avoir une classe Python simple comme celle-ci:

class Spam(object):
    __init__(self, description, value):
        self.description = description
        self.value = value

Je voudrais vérifier les contraintes suivantes:

  • "la description ne peut pas être vide"
  • "la valeur doit être supérieure à zéro"

Devrais-je:
1. valider les données avant de créer l'objet spam ?
2. vérifier les données dans la méthode __init__ ?
3. créer une méthode is_valid dans la classe Spam et l'appeler avec spam.isValid() ?
4. créer une méthode statique is_valid dans la classe Spam et l'appeler avec Spam.isValid(description, valeur) ?
5. vérifier les données lors de la déclaration des setters ?
6. etc.

Pouvez-vous recommander une approche bien conçue/pythonique/pas verbeuse (sur une classe avec de nombreux attributs)/élégante ?

139voto

Marcelo Cantos Points 91211

Vous pouvez utiliser des propriétés en Python pour appliquer proprement des règles à chaque champ séparément, et les appliquer même lorsque le code client tente de changer le champ :

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, d):
        if not d: raise Exception("description ne peut pas être vide")
        self._description = d

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("la valeur doit être supérieure à zéro")
        self._value = v

Une exception sera levée lors de toute tentative de violation des règles, même dans la fonction __init__, auquel cas la construction de l'objet échouera.

METTRE À JOUR : À un moment donné après 2010, j'ai appris à propos de operator.attrgetter:

import operator

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    description = property(operator.attrgetter('_description'))

    @description.setter
    def description(self, d):
        if not d: raise Exception("description ne peut pas être vide")
        self._description = d

    value = property(operator.attrgetter('_value'))

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("la valeur doit être supérieure à zéro")
        self._value = v

13voto

Dave Kirby Points 12310

Si vous voulez seulement valider les valeurs lorsque l'objet est créé ET que passer des valeurs invalides est considéré comme une erreur de programmation, alors j'utiliserais des assertions :

class Spam(object):
    def __init__(self, description:str, value:int):
        assert description != ""
        assert value > 0
        self.description = description
        self.value = value

Cela représente de manière concise que ce sont des préconditions pour la création de l'objet.

8voto

Jochen Ritzel Points 42916

À moins que vous ne soyez déterminé à créer votre propre solution, vous pouvez tout simplement utiliser formencode. Il brille vraiment avec de nombreuses attributs et schémas (il suffit de sous-classer les schémas) et dispose de nombreux validateurs utiles intégrés. Comme vous pouvez le voir, c'est l'approche "valider les données avant de créer un objet spam".

from formencode import Schema, validators

class SpamSchema(Schema):
    description = validators.String(not_empty=True)
    value = validators.Int(min=0)

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

## la manière dont vous validez réellement dépend de votre application
def validate_input( cls, schema, **input):
    data = schema.to_python(input) # valider le dictionnaire `input` avec le schéma
    return cls(**data) # il est validé ici, sinon il y aurait eu une exception

# renvoie un objet Spam
validate_input( Spam, SpamSchema, description='ça marche', value=5) 

# déclenche une exception avec tous les champs invalides
validate_input( Spam, SpamSchema, description='', value=-1) 

Vous pourriez également effectuer les vérifications pendant __init__ (et les rendre complètement transparentes avec des descripteurs|décorateurs|métaclasses), mais je ne suis pas vraiment fan de ça. J'aime une barrière nette entre l'entrée de l'utilisateur et les objets internes.

7voto

SilentGhost Points 79627

Si vous voulez valider uniquement les valeurs passées au constructeur, vous pouvez faire :

class Spam(object):
    def __init__(self, description, value):
        if not description or value <=0:
            raise ValueError
        self.description = description
        self.value = value

Cela ne va évidemment pas empêcher quelqu'un de faire quelque chose comme ceci :

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0

Ainsi, l'approche correcte dépend de ce que vous essayez d'accomplir.

3voto

smarie Points 453

Vous pouvez essayer pyfields :

from pyfields import field

class Spam(object):
    description = field(validators={"description can not be empty": lambda s: len(s) > 0})
    value = field(validators={"value must be greater than zero": lambda x: x > 0})

s = Spam()
s.description = "hello"
s.description = ""  # <-- lève une erreur, voir ci-dessous

Cela donne

ValidationError[ValueError]: Error validating [<...>.Spam.description=''].
  InvalidValue: description can not be empty. 
  Function [] returned [False] for value ''.

C'est compatible avec python 2 et 3.5 (contrairement à pydantic), et la validation se produit à chaque fois que la valeur est modifiée (pas seulement la première fois, contrairement à attrs). Il peut créer le constructeur pour vous, mais ne le fait pas par défaut comme montré ci-dessus.

Notez que vous pouvez également utiliser mini-lambda plutôt que de simples fonctions lambda si vous souhaitez des messages d'erreur encore plus directs (ils afficheront l'expression en erreur).

Consultez la documentation pyfields pour plus de détails (je suis l'auteur d'ailleurs ;) )

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