FriskyGrub
FriskyGrub

Reputation: 1049

Is there a pythonic way to check list elements, and stop at end of list (without an error)?

I have a complicated back end of a system, but i've tried to make a simple example so you know what I'm talking about.

Essentially I have a list of IDs, a user (via a gui) will cycle through this list generating IDs.

At some point, they want to go through and fix all the ones that they messed up or skipped (these will all have the same ID); this is where I have a problem.

I've written something that does exactly that (checking the list for bad IDs), but the problem is once it reaches the end of the list, it throws an index error. I want to cleanly exit the while-loop, and the function instead

def nextID():
    '''
        increment counter until we find a junk ID
    '''
    global tindx

    skips = 0
    while IDs[tindx] != 'junk_id' and tindx != len(IDs):
        print 'This ID is good: %s' %IDs[tindx]
        skips+=1
        tindx+=1    
    print 'Skipped %i IDs' %(skips), 'tindx is now',tindx

This is designed (again a huge simplification) to be used in the following way

tindx = 0

IDs = ['abc','bcd','cde','junk_id','junk_id','def','efg','junk_id','fgh','ghi']

# This is all hadled with in an interactive GUI
# User wants next ID
nextID()
# User changes ID
IDs[tindx] = 'good_id!'
# User wants next ID
nextID()
# User changes ID
IDs[tindx] = 'another_good_id'
# etc .... 

I know that if I switch the order of the while loop conditions, it will avoid the automatic IndexError, but the user won't know they've hit the end of the list and will try to change the ID (getting the same error).

I want to tell the user they're done run a save_IDs() function, and exit the program. This side of redesigning the software, is there a better way than adding the following if condition

while  ...

   if tindx == len(IDs):
      print 'you\'re done'
      save_IDs()
      return None

Upvotes: 0

Views: 121

Answers (1)

Pyonsuke
Pyonsuke

Reputation: 76

As you have mentioned, you do need to switch the arguments on the while:

while tindx != len(IDs) and IDs[tindx] != 'junk_id':
    ...

Otherwise it will first try to access IDs[tindx], and then check if it's invalid to do that.

For the problem, you could have nextID return the new index or None when it reaches the end:

def nextID(last_offset):
    offset = last_offset

    while offset < len(IDs) and IDs[offset] != 'junk_id'
        offset += 1

    if offset >= len(IDs):
        return None

    if offset > last_offset:
        print("Skipped %d IDs" % (offset - last_offset))

    return offset

Then to use it:

current_offset = 0
while doing things:
    current_offset = nextID(current_offset)
    if current_offset == None:
        # All done.
        break

    print("New offset: %d" % (offset))

    if user changes id:
        IDs[current_offset] = 'good_id!'
    else:
        # If the user didn't change the ID for whatever reason,
        # you'll need to manually increment current_offset.
        current_offset += 1

print("All done, saving..")
saveIDs()

If you're in callbacks inside a GUI then you might have something more like this:

def userChangedID(new_value):
    global current_offset

    IDs[current_offset] = new_value
    current_offset = nextID(current_offset)

    if current_offset == None:
        finish()

def userSkippedID():
    global current_offset

    current_offset = nextID(current_offset + 1)

    if current_offset == None:
        finish()

def finish():
    global finished

    finished = True
    print("All done, saving...")
    saveIDs()

Ideally a method should have as few side-effects as it needs to do what it should be doing.

In the original code, nextID() was not only 'finding the next ID', but it was also mutating the state of the entire process.

This makes it fairly misleading from a developer's perspective, who might just be expecting nextID() to find and return the next ID.

Upvotes: 1

Related Questions