Poprawne podejście do walidacji atrybutów instancji klasy

Posiadanie prostej klasy Pythona takiej jak Ta:

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

Chciałbym sprawdzić następujące ograniczenia:

  • "opis nie może być pusty"
  • "wartość musi być większa od zera"

Powinienem:
1. zweryfikować dane przed utworzeniem obiektu spam ?
2. sprawdzić dane w metodzie __init__?
3. Utwórz metodę is_valid na klasie Spam i wywołaj ją ze spamem.isValid() ?
4. Utwórz statyczną metodę is_valid na klasie Spam i wywołaj ją za pomocą Spam.isValid (opis, wartość) ?
5. sprawdź dane deklaracji seterów ?
6. itd.

Czy mógłbyś polecić dobrze zaprojektowane / Pythoniczne / nie wyraziste (na klasie z wieloma atrybutami) / eleganckie podejście?

Author: systempuntoout, 2010-05-13

4 answers

Możesz użyć właściwości Pythona , Aby zastosować reguły do każdego pola osobno i egzekwować je nawet wtedy, gdy kod klienta próbuje zmienić pole:

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 cannot be empty")
        self._description = d

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

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

Wyjątek będzie rzucany przy każdej próbie naruszenia reguł, nawet w funkcji __init__, w którym to przypadku budowa obiektu zakończy się niepowodzeniem.

UPDATE: gdzieś między 2010 a teraz dowiedziałem się o 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 cannot be empty")
        self._description = d

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

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v
 71
Author: Marcelo Cantos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 03:01:51

Jeśli chcesz zweryfikować wartości tylko wtedy, gdy obiekt jest tworzony i przekazanie nieprawidłowych wartości jest uważane za błąd programowania, użyłbym twierdzeń:

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

Jest to tak zwięzłe, jak masz zamiar uzyskać, i wyraźnie dokumentuje, że są to warunki wstępne do tworzenia obiektu.

 6
Author: Dave Kirby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 12:32:51

Jeśli nie jesteś hellbent na toczenie własne, można po prostu użyć formencode . Naprawdę świeci wieloma atrybutami i schematami (tylko schematy podklas) i ma wiele przydatnych walidatorów wbudowanych. Jak widać jest to podejście "validate data before creating spam object".

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

## how you actually validate depends on your application
def validate_input( cls, schema, **input):
    data = schema.to_python(input) # validate `input` dict with the schema
    return cls(**data) # it validated here, else there was an exception

# returns a Spam object
validate_input( Spam, SpamSchema, description='this works', value=5) 

# raises an exception with all the invalid fields
validate_input( Spam, SpamSchema, description='', value=-1) 

Podczas __init__ Możesz również wykonać kontrole (i uczynić je całkowicie przejrzystymi za pomocą deskryptorów / dekoratorów / metaclass), ale nie jestem wielkim fanem tego. Lubię czystą barierę między użytkownikami obiekty wejściowe i wewnętrzne.

 6
Author: Jochen Ritzel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 13:41:51

Jeśli chcesz zweryfikować tylko te wartości przekazane do konstruktora, możesz to zrobić:

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

To oczywiście nie przeszkodzi nikomu w zrobieniu czegoś takiego:

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0
Więc, poprawne podejście zależy od tego, co próbujesz osiągnąć.
 3
Author: SilentGhost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 09:22:46