2016-05-05 2 views
1

У меня есть класс, который реализует атрибуты через @property декоратора следующиммногоразовый класс Validating атрибуты

class num(object): 

    def __init__(self, value): 
     self.val = value 

    @property 
    def val(self): 
     return self._val 

    @val.setter 
    def val(self, value): 
     validation(self, (int, float), value, 0.0) 

Когда значение атрибута должно быть установлено, оно должно получить проверенную.

def validation(obj, types, value, default): 
    """Checks if the input provided for the attribute is valid.""" 
    try: 
     if not isinstance(value, types): 
      obj._val = default 
      raise TypeError 
     if value < 0: 
      obj._val = default 
      raise ValueError 
     else: 
      obj._val = float(value) 
    except ValueError as e: 
     print("Should be a positive number. Value set at ", default) 
    except TypeError: 
     print("Should be a number. Value set at ", default) 

Этот код работает для данного конкретного случая, когда у меня есть только один априори известный атрибут val. Я хотел бы сделать функцию validation более общей, чтобы иметь возможность использовать для других атрибутов (например, произвольный индекс). Я вижу, что моя проблема заключается в obj._val, где я нарушаю инкапсуляцию.

Я думаю, что одним из возможных решений является использование декоратора метода, связанного с установщиком атрибута, или, возможно, передача дополнительного аргумента validation, чтобы знать, какой атрибут должен быть проверен. Или, возможно, использует совершенно другой подход.

Заранее благодарен!

+0

Почему ваш валидатор игнорирует «default»? –

+0

@MartijnPieters ошибка в моем упрощенном тестовом коде. –

ответ

2

Я бы сказал, что один из самых чистых/многоразовых подходов для достижения этого в python - с дескрипторами. Методы get/set в стиле java просто слишком подробны для такого рода требований.

Вот что ваша модель будет выглядеть - всего 3 линии:

class Person(object): 
    balance = Integer('_balance') 
    age = PositiveInteger('_age') 

Несколько сценариев:

person = Person() 

person.balance = 0 # OK 
person.balance = '0' # OK 
person.balance = 'wrong' # TypeError 

person.age = 30 # OK 
person.age = '30' # OK 
person.age = -1 # ValueError 
person.age = 'wrong' # TypeError 

реализации описателей Integer и PositiveInteger:

class Integer(object): 
    def __init__(self, name, default=None): 
     self.name = name 
     self.default = default 

    def __get__(self, instance, owner): 
     return getattr(instance, self.name, self.default) 

    def clean(self, value): 
     if isinstance(value, int) or str(value).isdigit(): 
      return int(value) 
     return value 

    def __set__(self, instance, value): 
     if isinstance(self.clean(value), int): 
      setattr(instance, self.name, value) 
     else: 
      raise TypeError('`{}` not a valid integer'.format(value)) 


class PositiveInteger(Integer): 
    def clean(self, value): 
     x = super(PositiveInteger, self).clean(value) 
     if isinstance(x, int) and x < 0: 
      raise ValueError('`{}` is not a positive integer'.format(value)) 
     return x 
+0

Выглядит интересно, но так как ваш пример не работает. Я могу сделать недействительные присваивания без блокировки и не сообщать об этом. –

+0

hmm Я снова проверил код, кажется, что он создает исключения только в сценариях выше, как и ожидалось. что именно вы делаете и не сообщают? – fips

+0

Извините. Да, он отлично работает. Но теперь у меня другая проблема, которая не может использовать метод __init__ для заполнения атрибутов. –

1
def val(self, value): 
    self._val = validation((int, float), value, 0.0) 

def validation(types, value, default): 
    """Checks if the input provided for the attribute is valid.""" 
    if not isinstance(value, types): 
     print("Should be a number. Value set at ", default) 
    elif value < 0: 
     print("Should be a positive number. Value set at ", default) 
    else: 
     return value 
    return default 

Нет необходимости поднять исключения и немедленно их поймать. Печать сразу прекрасна. Однако использование их повлияло на поток программы, поэтому не было необходимости в else. Поскольку я не использовал их, мне нужен не только else, мне нужен elif для второго блока.

Мне нравится уменьшать дублирование, следовательно, код выше, как он есть. Для удобства чтения и надежности может быть лучше, чтобы сделать одно из следующих действий:

if ... 
    print ... 
    return default 
if ... 
    print ... 
    return default 
# Optionally add an else 
return value 

или

if ... 
    print ... 
    result = default 
elif ... 
    print ... 
    result = default 
else: 
    result = value 
return result 

или если вы хотите, чтобы сделать его немного короче, повторно использовать параметр:

if ... 
    print ... 
    value = default 
elif ... 
    print ... 
    value = default 
return value 

Другим кратким подходом (для которого существуют подобные варианты) является одновременное обращение ко всем ошибкам. Как и сокращение кода, это означает, что пользователь знает обо всех требованиях за один раз и не фиксирует одну проблему только для того, чтобы встретить другой.

if not isinstance(value, types) or value < 0: 
    print("Should be a positive number. Value set at ", default) 
    return default 
return value 

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

Также обратите внимание, что ваша оригинальная функция действительно не использовала default, настройка на 0 была жестко запрограммирована, что плохо.

4

Оставьте ваш валидатор только только подтвердите. Попросите его возбудить исключение, если значение недействительно, и не более сделать преобразование float для целых чисел.Ваш валидатор слишком специфичен для чисел, поэтому переименовать его (и используй другие валидатор для других типов):

def is_positive_number(value): 
    """Checks if the input provided is an actual number, returns a float.""" 
    if not isinstance(value, types): 
     raise TypeError('Not a number') 
    if value < 0: 
     raise ValueError('Must be a positive number') 
    return float(value) 

установки атрибута в инкубатор Отпуск; он может также принять решение по умолчанию:

@val.setter 
def val(self, value): 
    self._val = 0.0 # default, in case validation fails 
    self._val = validation(value) 

Теперь валидатор больше не связывается с определенным атрибутом. Другие, более сложные валидаторы могут быть переданы больше контекста, если это необходимо.

Обратите внимание, что обработка ошибок (улавливание исключения проверки и печать сообщения) должна действительно иметь место за пределами. Печать внутри валидатора слишком сильно сочетает ваш код с определенным режимом взаимодействия; вы не можете теперь использовать этот код с веб-интерфейсом или графическим интерфейсом, например.

+0

Где бы вы поместили обработку ошибок? –

+0

В коде, который пытается установить значение? Обратите внимание, что код, который получил значение от пользователя, может * повторить попытку *, запросив у пользователя новое значение. Если валидатор просто проигнорировал значение и задал значение по умолчанию, код, запрашивающий значение, не может отличить неправильное значение, введенное от пользователя, вводящего значение по умолчанию. –

Смежные вопросы