Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python Python Collections (Retired) Lists Redux Manipulating Lists

Tiger Rahman
Tiger Rahman
1,333 Points

Try/Except failing to remove item

I recognize that I can name the index and use that to explicitly remove the remaining [1,2,3] list; after removing "a" and "False", the_list should be: [1,2,3,[1,2,3]] so we'd delete the item at index 3.

Programmatically, it seems that you should also be able to remove an item that throws a TypeError using try and except. You shouldn't be able to divide a string (like 'a') or a list (like [1,2,3]) by 1; both should throw errors allowing for Try/Except to do this cleanup.

However, when I run this code it only works for the string -- I'm assuming I'm failing to set up the loop somehow to iterate correctly.

Observations would be great!

lists.py
the_list = ["a", 2, 3, 1, False, [1, 2, 3]]

# Your code goes below here
the_list.insert(0, the_list.pop(3))
for item in the_list:
    if str(item) == 'False' or str(item) == 'True':
        the_list.remove(item)    
    else:
        try: 
            item = (item/1)
        except:
            the_list.remove(item)
            continue
print (the_list)

4 Answers

Gavin Ralston
Gavin Ralston
28,770 Points

When you're iterating over a list and then remove an item at the current index, you'll skip what would have been the next item in the list.

Try this with a list that looks like the contents of your list in step 2, after you've inserted the number 1 to the first spot in the array:

list = [1, "a", 2, 3, False, [1, 2, 3]]

for item in list:
    if item == "a":
        list.remove(item)
    print(item)

You should get 1, 2, 3, False, [1, 2, 3] in your list (the item you're working on) but you'll print out 1, a, 3, False, [1, 2, 3] instead.

Because the second time through you removed an item at index 1, you shifted the rest of the list to the "left".

This has two consequences:

First, you print the item you just removed from the list when you probably didn't want to.

Second, you skip the next item in the list because it is now at your current index you just worked on and so on the next iteration, you effectively skip over an element by working on the "next next" element. :)

Now look at this loop's output versus the contents of the list after it runs, which effectively does the same thing you're doing, just not using TypeErrors:

list = [1, "a", 2, 3, False, [1, 2, 3]]

for item in list:
    if item == "a" or item == False:
        list.remove(item)
    print(item)

print(list)

If you try it like this, with an else statement to get around the "I'm printing the wrong value bit" you'll wind up with even weirder output:

list = [1, "a", 2, 3, False, [1, 2, 3]]

for item in list:
    if item == "a" or item == False:
        list.remove(item)
    else:
        print(item)

print(list)   #prints 1 and 3 in the loop, but the list is, again, modified how you want it to be...

The list looks the way you want it, but while you're iterating over it you're skipping indices and missing a lot of stuff. In fact, you're only getting a list back the way you want it to look because there's only one instance of any item you want to remove from the list. Check this one out to see how all kinds of things get skipped over that you probably don't want to skip:

list = [1, "a", False, 2, "a", "a", 3, False, [1, 2, 3]]

for item in list:
    if item == "a" or item == False:
        list.remove(item)
    else:
        print(item)

print(list)

Hope that helps a little.

Tiger Rahman
Tiger Rahman
1,333 Points

Thanks for this -- helps to reveal the actions behind the scene. Hadn't thought about how these things might shift out of place.

Gavin Ralston
Gavin Ralston
28,770 Points

Glad I could help a little.

Also, if you're going to check types, check the types.:)

In this case you're using a try/except block, which is really designed to handle a potential (unforseen, uncontrollable) problem at runtime.

In your case, you want to actively hunt down those items and do something with them. You can foresee this "problem" of having items which aren't integers or lists, and in fact, that's exactly what your code is trying to solve.

Ideally in Python you don't do type-checking at all, but there are always exceptions.

Super brittle, as this code handles this case specifically, and nothing else, including having two or more strings or booleans next to each other in the list. It's just there to show you how to check type if you were interested. :)

the_list = ["a", 2, 3, 1, False, [1, 2, 3]]

# Your code goes below here
the_list.insert(0, the_list.pop(3))
for item in the_list:
    if isinstance(item, str) or isinstance(item, bool):
        the_list.remove(item)    

print (the_list)
Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,064 Points

Since you are operating on the same iterable as the for loop is using, by changing the size of the_list with removes the for loop ends early.

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,064 Points

If you are going to you use isinstance() you can simplify the code by removing remove "bool" or "not int":

for item in the_list[:]:  # <-- added [:]
    if isinstance(item, bool) or not isinstance(item, int):
        the_list.remove(item)

Note the added [:] to have the for loop iterate on a copy of the_list which insulates it from remove changes.

EDIT updated code to catch subclass type bool

Gavin Ralston
Gavin Ralston
28,770 Points

Definitely work on list copies.

I just tried to put isinstance in the context of his script as an aside, but that shows how easy it is to make stuff more...sound. :)

If there's one takeaway from this, it's "Don't modify lists in place, use a copy."

It'll definitely be reinforced in the Python courses.

However, the code above doesn't work as you might expect because:

issubclass(bool, int)  # True

isinstance([1], int)  # False

Which means False will qualify as an int and not be removed, and the list will not be an int and get removed.

The idea that you should work on an array copy and then have fun digging around into the how's and why's should be evident, by now, though. ;)

Chris Freeman
Chris Freeman
Treehouse Moderator 68,064 Points

Thanks Gavin. I have update my answer to consider type bool

Gavin Ralston
Gavin Ralston
28,770 Points

No problem, my post was heavily edited because at first I was like "brilliant! I should've just done that!" and then I thought... waiiit a second, bool subclasses int, so it won't remove False. True and False are ints 1 and 0, respectively, in almost every case but when their tostring method gets called.

So you could rewrite the example that started all this with:

the_list.insert(False, the_list.pop(3))

...and that would work the same.

Which, unfortunately, means that this doesn't work as expected:

the_list = [True, 1, False]

for item in the_list[:]: 
    if not isinstance(item, int):
        the_list.remove(item)    

print (the_list)

It'll return everything. :/

Here's more on booleans being integers in Python 3

Tiger Rahman
Tiger Rahman
1,333 Points

Got it -- seeing more about copies of lists with slicing now. Had definitely encountered the issue of bools being frighteningly easy to misidentify as ints and therefore came to the semi-bogus literal matching as string.