Правильный подход к проверке атрибутов экземпляра класса - PullRequest
64 голосов
/ 13 мая 2010

Наличие простого класса Python, такого как:

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

Я хотел бы проверить следующие ограничения:

  • "описание не может быть пустым"
  • «значение должно быть больше нуля»

Должен ли я:
1. проверить данные перед созданием спам-объекта?
2. проверить данные по __init__ методу?
3. создать метод is_valid для класса Spam и вызвать его с помощью spam.isValid ()?
4. создать статический метод is_valid в классе Spam и вызвать его с помощью Spam.isValid (description, value)?
5. проверить данные декларации сеттеров?
6. и т. Д.

Не могли бы вы порекомендовать хорошо разработанный / Pythonic / не многословный (для класса с множеством атрибутов) / элегантный подход?

Ответы [ 4 ]

83 голосов
/ 13 мая 2010

Вы можете использовать свойства Python , чтобы аккуратно применять правила к каждому полю отдельно и применять их, даже когда клиентский код пытается изменить поле:

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

Исключение будет выдано при любой попытке нарушить правила, даже в функции __init__, и в этом случае построение объекта завершится неудачей.

ОБНОВЛЕНИЕ: Где-то между 2010 и сейчас, я узнал о 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
7 голосов
/ 13 мая 2010

Если вы хотите проверять значения только тогда, когда объект создан И передача недопустимых значений считается ошибкой программирования, тогда я бы использовал утверждения:

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

Это примерно так же кратко, как вы собираетесь получить, и четко документирует, что это предварительные условия для создания объекта.

6 голосов
/ 13 мая 2010

Если вы не склонны кататься, вы можете просто использовать форменкод . Он действительно сияет множеством атрибутов и схем (только схемы подклассов) и имеет множество полезных встроенных валидаторов. Как вы можете видеть, это подход «проверка данных перед созданием объекта спама».

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) 

Вы могли бы также выполнять проверки во время __init__ (и сделать их полностью прозрачными с помощью дескрипторов | декораторов | метакласса), но я не большой поклонник этого. Мне нравится чистый барьер между пользовательским вводом и внутренними объектами.

5 голосов
/ 13 мая 2010

, если вы хотите проверить только те значения, которые передаются в конструктор, вы можете сделать:

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

Это, конечно, никому не помешает сделать что-то вроде этого:

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

Итак, правильный подход зависит от того, чего вы пытаетесь достичь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...