Шаблон проектирования Python для многих условий

Какова рекомендуемая структура для написания функций проверки со многими условиями? См. Два примера. Первое выглядит уродливым, второе не очень распространено, возможно, потому, что assert обычно используется, чтобы исключить неожиданное поведение. Есть ли лучшие альтернативы?

def validate(val):
  if cond1(val):
    return False
  if cond2(val):
    return False
  if cond3(val)
    return False
  return True

или

def validate(val):
  try:
    assert cond1(val)
    assert cond2(val)
    assert cond3(val)
    return True
  except AssertionError:
    return False

Ответы

Ответ 1

Компактный способ записи этой функции - использовать any и выражение генератора:

def validate(val):
    conditions = (cond1, cond2, cond3)
    return not any(cond(val) for cond in conditions)

Функции any и all работают в коротком замыкании, поэтому они прекращают тестирование, как только они имеют определенный результат, т.е. any останавливается, как только он достигает значения True-ish, all останавливается, как только он достигает значения False-ish, поэтому эта форма тестирования достаточно эффективна.

Я также должен отметить, что гораздо эффективнее передать выражение генератора, подобное этому, в all/any, чем понимание списка. Поскольку all/any прекратить тестирование, как только они получат правильный результат, если вы их подаете из генератора, тогда генератор тоже остановится, поэтому в приведенном выше коде, если cond(val) оценивает значение True-ish no будут проверены дополнительные условия. Но если вы передадите all/any понимание списка, например any([cond(val) for cond in conditions]), весь список должен быть создан до того, как all/any может даже начать тестирование.


Вы не указали нам внутреннюю структуру ваших функций cond, но вы упомянули assert в своем вопросе, поэтому я чувствую, что здесь следующие замечания.

Как я уже упоминал в комментариях, assert не должен использоваться для проверки данных, он используется для проверки логики программы. (Кроме того, управление утверждениями можно отключить с помощью опции командной строки -O). Правильное исключение для использования с данными с недопустимыми значениями - ValueError, а для объектов, которые являются неправильным типом, используйте TypeError. Но имейте в виду, что исключения предназначены для обработки ситуаций, которые являются исключительными.

Если вы ожидаете много искаженных данных, тогда, как правило, более эффективно использовать логику на основе if, чем исключения. Обработка исключений Python довольно быстро, если исключение фактически не создано, на самом деле оно быстрее, чем эквивалентный код на основе if. Однако, если возникло исключение, говорят, что более 5-10% времени, то код try...except будет заметно медленнее, чем эквивалент if.

Конечно, иногда использование исключений - единственный разумный вариант, хотя ситуация не является настолько исключительной. Классическим примером является преобразование коллекции числовых строк в реальные числовые объекты, так что строки, представляющие целые числа, преобразуются в целые объекты, другие числовые строки преобразуются в поплавки, а другие строки остаются в виде строк. Стандартный способ сделать это в Python включает использование исключений. Например:

def convert(s):
    ''' Convert s to int or float, if possible '''
    try:
        return int(s)
    except ValueError:
        try:
            return float(s)
        except ValueError:
            return s

data = ['42', 'spam', '2.99792458E8']
out = [convert(u) for u in data]
print(out)
print([type(u) for u in out])

Выход

[42, 'spam', 299792458.0]
[<class 'int'>, <class 'str'>, <class 'float'>]

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

Ответ 2

def valid(value):
    return (is_this(value)
            and that(value)
            and other(value))
Оператор

and демонстрирует поведение "короткого замыкания" в Python.

Ответ 3

Первый способ намного лучше. Его можно немного приукрасить с помощью any():

def validate_conditions(value):
    return not any((condition(value) for condition in conditions))

Ответ 4

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

def validate(val):
    return (cond1 and cond2 and cond3)

Или, чтобы отменить это (как в вашем примере):

def validate(val):
    return not (cond1 and cond2 and cond3)