Reputation: 1049
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
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