views:

436

answers:

4

The code below is based on this recipe. However, the key point of the recipe - that it provides a way to break out of the iteration on an iterator if the iterator is empty - doesn't seem to work here, instead behaving in the following undesired ways:

  1. If get_yes_no_answer() == False and there are two or more items left in the iterator, next_choice is skipped, rather than being selected on the next iteration.
  2. If get_yes_no_answer() == False and there are less than two items left in the iterator, my_func() returns None.

How can I ensure that:

  • If get_yes_no_answer() == False and there are two or more items left in the iterator, next_choice is not skipped?
  • If get_yes_no_answer() == False and there is one item left in the iterator, my_func() prints it and calls get_yes_no_answer()?
  • If get_yes_no_answer() == False and there are no items left in the iterator, the except StopIteration clause is triggered?

Here's the code:

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)
+3  A: 

why are you doing it this way at all? why not just:

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')

I don't know what your is the length of your input but I doubt it's worth the trouble.

SilentGhost
I'm using finditer() instead of findall() because I need to know the start and end locations of the match. If I'm not mistaken, I won't be able to call start() and end() methods on the results from findall() because they are not MatchObjects.
sampablokuper
so, why is this information is not in your question? please add it. When you `print choice`, it is very misleading since user doesn't expect to see something like this: `<_sre.SRE_Match object at 0x0104AFA8>`.
SilentGhost
Apologies for this. I actually had a <pre>.group()</pre> after the <pre>print choice</pre> when I was pasting in the code block and I clearly deleted it by accident in the process of attempting to edit the code for clarity while composing my question. I've put it back in now and I've voted up your comment by way of thanks.
sampablokuper
A: 

See the pairwise iterator from this question. You can then check for last item like this:

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()

In the example you showed this doesn't seem to be necessary. You don't need to check if finditer returned an iterator because it always does. And you can just fall through the for loop if you don't find what you want:

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()
Ants Aasma
`else` will be reached if either iterator is empty or nothing was returned from within the `for` loop.
SilentGhost
I used else purely for readability, but as an additional bonus you can break out and do something with choice within the same function before returning.
Ants Aasma
A: 

Why so complex approach?

I think this should do pretty much the same:

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
Kimvais
See my reply to SilentGhost's answer: http://stackoverflow.com/questions/1491957/testing-for-an-empty-iterator-in-a-python-for-loop/1492039#1492039
sampablokuper
+1  A: 

You don't need to check if the iterator is empty. The for loop will do that for you, and stop when the iterator is empty. It's as simple as that.

Also, you don't need the else after the sys.exit() or the return.

That done, your code looks like this:

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()

That's it!

What happens is that you in the loop, also gets the next item. The result is that you in fact only show every second answer.

In fact, you can simplify it even more:

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()

Iterators are used pretty much as any sequence type. You don't need to treat it differently from a list.

Lennart Regebro
Bingo! I was close, but no cigar. Thank you :)
sampablokuper
The second solution you've proposed, I don't like so much. It's important to distinguish between having no matches and having no choices. But thanks anyhow.
sampablokuper