Тестирование пустого итератора в цикле Python for

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

  1. Если get_yes_no_answer () == False и в итераторе осталось два или более элемента, next_choice пропускается, а не выбирается на следующей итерации.
  2. Если get_yes_no_answer () == False и в итераторе осталось менее двух элементов, my_func () возвращает None.

Как я могу убедиться, что:

  • Если get_yes_no_answer () == False и в итераторе осталось два или более элемента, next_choice не пропускается?
  • Если get_yes_no_answer () == False и в итераторе остался один элемент, my_func () печатает его и вызывает get_yes_no_answer ()?
  • Если get_yes_no_answer () == False и в итераторе не осталось элементов, срабатывает предложение кроме StopIteration?

Вот код:

def my_func(choice_pattern, input):
# Search in input for some things to choose from.
choice_iterator = choice_pattern.finditer(input, re.M)
if not choice_iterator:
    print "No choices. Exiting..."
    sys.exit()
else:
    # Show choices to the user. For each one, ask user for a yes/no response. If
    # choice accepted, return a result. Otherwise show user next choice. If no
    # choices accepted by user, quit.
    for choice in choice_iterator:
        print choice.group()
        # get_yes_no_answer() returns True or False depending on user response.
        if get_yes_no_answer():
            return choice
        else:
            # Check if iterator is empty. If so, quit; if not, do something else.
            try:
                next_choice = choice_iterator.next()
            except StopIteration:
                print "No matches. Exiting..."
                sys.exit()
            else:
                choice_iterator = itertools.chain([next_choice], choice_iterator)

person Community    schedule 29.09.2009    source источник


Ответы (4)


Вам не нужно проверять, пуст ли итератор. Цикл for сделает это за вас и остановится, когда итератор станет пустым. Это так просто.

Кроме того, вам не нужен else после sys.exit () или возврата.

После этого ваш код выглядит так:

def my_func(choice_pattern, input):
    # Search in input for some things to choose from.
    choice_iterator = choice_pattern.finditer(input, re.M)
    if not choice_iterator:
        print "No choices. Exiting..."
        sys.exit()

    # Show choices to the user. For each one, ask user for a yes/no response. If
    # choice accepted, return a result. Otherwise show user next choice. If no
    # choices accepted by user, quit.
    for choice in choice_iterator:
        print choice
        # get_yes_no_answer() returns True or False depending on user response.
        if get_yes_no_answer():
            return choice
    # Loop exited without matches.
    print "No matches. Exiting..."
    sys.exit()

Вот и все!

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

Фактически, вы можете упростить его еще больше:

def my_func(choice_pattern, input):
    choice_iterator = choice_pattern.finditer(input, re.M)
    if choice_iterator:
        for choice in choice_iterator:
            print choice
            if get_yes_no_answer():
                return choice
    # If there is no choices or no matches, you end up here:
    print "No matches. Exiting..."
    sys.exit()

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

person Lennart Regebro    schedule 29.09.2009
comment
Бинго! Я был рядом, но без сигары. Спасибо :) - person ; 29.09.2009
comment
Второе решение, которое вы предложили, мне не очень нравится. Важно различать отсутствие совпадений и отсутствие выбора. Но все равно спасибо. - person ; 29.09.2009

зачем ты вообще это делаешь? почему бы и не просто:

def get_choice(pattern, inpt):
    choices = pattern.finditer(inpt, re.M)
    if not choices:
        sys.exit('No choices')
    for choice in choices:
        print(choice.group(0))
        if get_yes_no_answer():
            return choice
    sys.exit('No matches')

Я не знаю, какова длина вашего ввода, но я сомневаюсь, что это того стоит.

person SilentGhost    schedule 29.09.2009
comment
Я использую finditer () вместо findall (), потому что мне нужно знать начало и конец совпадения. Если я не ошибаюсь, я не смогу вызывать методы start () и end () для результатов findall (), потому что они не являются MatchObjects. - person ; 29.09.2009
comment
Итак, почему эта информация не в вашем вопросе? пожалуйста, добавьте это. Когда вы print choice, это вводит в заблуждение, поскольку пользователь не ожидает увидеть что-то вроде этого: <_sre.SRE_Match object at 0x0104AFA8>. - person SilentGhost; 29.09.2009
comment
Приносим извинения за это. На самом деле у меня была ‹pre› .group () ‹/pre› после выбора ‹pre› печати ‹/pre›, когда я вставлял блок кода и явно удалил его случайно в процессе попытки редактирования кода для ясности при составлении моего вопроса. Я положил его обратно и проголосовал за ваш комментарий в качестве благодарности. - person ; 29.09.2009

См. Попарный итератор из этого вопрос. Затем вы можете проверить последний элемент следующим образом:

MISSING = object()
for choice, next_choice in pairwise(chain(choice_iterator, [MISSING])):
    print(choice.group())
    if get_yes_no_answer():
        return choice.group()
    if next_choice is MISSING:
        print("No matches. Exiting...")
        sys.exit()

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

def my_func(choice_pattern, input):
    """Search in input for some things to choose from."""
    for choice in choice_pattern.finditer(input, re.M):
        print(choice.group())
        if get_yes_no_answer():
            return choice.group()
    else:
        print("No choices. Exiting...")
        sys.exit()
person Ants Aasma    schedule 29.09.2009
comment
else будет достигнут, если итератор пуст или ничего не было возвращено из цикла for. - person SilentGhost; 29.09.2009
comment
Я использовал else исключительно для удобочитаемости, но в качестве дополнительного бонуса вы можете выйти и сделать что-то с выбором в той же функции перед возвратом. - person Ants Aasma; 29.09.2009

Почему такой комплексный подход?

Я думаю, что это должно делать примерно то же самое:

def my_func(pattern, data):
  choices = pattern.findall(data, re.M)
  while(len(choices)>1):
    choice = choices.pop(0)
    if get_yes_no_answer():
      return choice
    else:
      choices.pop(0)
  else:
    return None
person Kimvais    schedule 29.09.2009
comment
См. Мой ответ на ответ SilentGhost: stackoverflow.com/questions/1491957/ - person ; 29.09.2009