Вложенные попытки/исключения или если/иначе – как определить, что использовать?

Я понимаю, что уже были дискуссии о том, использовать ли блоки If/Else или Try/Except. Такой вопрос находится здесь: Лучше "попробовать" что-то и поймать исключение или сначала проверить, возможно ли это, чтобы избежать исключения?

Но я хотел бы немного расширить обсуждение до вложенных try/except и вложенных if/elif/else логических блоков. Вот настройка... Я хочу написать функцию, которая позволит пользователям предоставлять строковый литерал, целое число или итерацию. Это функция высокого уровня, которая обеспечит некоторый уровень абстракции для других функций, которые я написал. Мой код такой:

def high_level_func(parameter = None):
    """
    :param parameter: Can accept the string 'All', a single integer, or an iterable such 
        as a range or a list or tuple of ints.
    """
    try:
        if parameter.lower() == 'all'
        # .lower because str input should be case-insensitive
            return str_all_function(parameter) # Only accepts the 
                    # string 'all' - case insensitive
    except AttributeError:
        # if parameter is an int or iter end up here because those types 
        # don't have .lower() methods
        try:
            for para in parameter:
                try:
                    print(int_input_function(parameter))
                except MyException:
                    raise MyException('An iter of something other than ints was '
                                      'provided and cause this error.')
        except TypeError:
            # parameter must be an int because ints aren't iterable and end up here
            return int_input_function(parameter)

В этом случае предположим, что я понятия не имею, какой тип ввода предпочтет большинство пользователей (т. е. равновероятно, что любой данный пользователь передаст либо int, либо iter, либо строку «все». Но мы можем с уверенностью предположить, что пользователь, скорее всего, никогда не передаст список строк или кортеж строк - недопустимые iters)

Можно ли это сделать, или мне лучше проверить тип ввода и выполнить блок кода if/elif/else (IEE)? По вашему мнению, кодовый блок IEE будет значительно легче читать?

Альтернативное предложение: как насчет использования комбинации try/except и IEE? Команда try/except может попытаться уменьшить ввод, если это, например, строковый литерал 'all', а IEE будет вложен в блок except для проверки альтернативных случаев (целое число, итерация или недопустимый тип)

И вообще, как я могу определить, какой метод самый быстрый, не написав функцию три раза и не протестировав каждый раз?

Еще один вопрос: если Try/Except в среднем быстрее, чем тест If/Elif/Else, но мы думаем, что If/Elif/Else имеет лучшую читаемость, насколько быстрее должен быть метод Try/Except, чтобы гарантировать читабельность броска в окно или читабельность всегда превалирует перед скоростью? Или это на усмотрение кодера/команды?


person boymeetscode    schedule 05.04.2017    source источник
comment
Python рекомендует модель EAFP (проще попросить прощения, чем разрешения), поэтому в этом случае использование try-except будет питоническим способом. Лично я бы пошел по пути, не связанному с питоном, и использовал бы if isinstance(parameter, collections.Iterable) для разделения проверок, чтобы избежать каскада try-excepts. Что касается производительности, я бы посоветовал вам написать более простую версию и протестировать ее с помощью модуля testit.   -  person Fernando Coelho    schedule 05.04.2017
comment
Для дополнительной информации я провел упрощенную версию этого сравнения на более чем 100 000 итераций и обнаружил, что If/Elif/Else с isInstance на самом деле быстрее, чем метод try/except. (Хотя я не использовал модуль testit.) Таким образом, кажется, что использование подхода If/Else в этом случае является беспроигрышным для удобства чтения и эффективности. Просто не уверен, почему. Метод Try/Except: 1,34000015258789 секунд на 100 000 циклов Метод If/Elif/Else: 1,18899989128112 секунд на 100 000 циклов   -  person boymeetscode    schedule 05.04.2017


Ответы (1)


Я думаю, что ваши функции ввода должны отвечать за проверку ввода, а не функция, которая их вызывает. После этого вы можете позволить функции высокого уровня оставаться на высоком уровне и продолжать использовать trying для использования одной из них, пока она не завершится успешно:

def high_level_function(parameter=None):
  try:
    return str_input_function(parameter)
  except ValueError:  # Raised by str_input_function on bad input.
    pass

  try:
    return iter_input_function(parameter)  # MyException now propagates itself.
  except ValueError:  # Raised by iter_input_function on bad input.
    pass

  try:
    return int_input_function(parameter)
  except ValueError:  # Raised by int_input_function on bad input.
    pass

  # This should never be hit:
  raise ValueError("parameter has unsupported type: " + type(parameter).__name__)
person Brian Rodriguez    schedule 05.04.2017
comment
Спасибо за это понимание. Он простой, чистый, читаемый и по-прежнему следует методологии EAFP для Python. Немного злюсь на себя за то, что слишком много думаю об этой проблеме. Я склонен делать это с python. Грррр. - person boymeetscode; 05.04.2017
comment
Без проблем! FWIW, я думаю, вы были на правильном пути. - person Brian Rodriguez; 05.04.2017
comment
Когда я достигну статуса return, я должен выйти из функции high_level_function, верно? Я тестирую свой код, и кажется, что я просматриваю все блоки try, несмотря на то, что каждый блок try имеет оператор возврата, и я ожидаю, что он преуспеет, но это не так. Убедиться, что то, что я ожидаю, и то, что делает python, одно и то же. - person boymeetscode; 06.04.2017
comment
Если он пройдет все, он выдаст это последнее исключение, вы видите, что это происходит? в противном случае это означает, что он вернулся раньше, чем ожидалось. - person Brian Rodriguez; 06.04.2017
comment
Извините, я понял это. Был вызван отсутствием функции int(). ‹ закатывает глаза› Таким образом, он выдавал ошибку AttributeError, как я и хотел, но не в том месте. :-/ - person boymeetscode; 06.04.2017