1

For some reason, the following block of code is only iterating through the for loop once, despite there being 2 entries in the list.

def remove_client(self, client):
    try:
        temp = client.followedby
        for i in temp:
            print("Begin")
            print(i)
            print(client.followedby)
            i.unfollow_user()
            print(client.followedby)
            print("Passed")
        print("Out of loop.")
    except AttributeError:
        print("AttributeError")
        pass
    self.cur_id[client.id] = False
    self.clients.remove(client)

The called function unfollow_user:

def unfollow_user(self):
    self.send_host_message("Stopped following at {}.".format(time.asctime(time.localtime(time.time()))))
    self.following.followedby.remove(self)
    self.following = ""
    print("end of unfollow user")

This should work as to my knowledge. It doesn't throw any error, with the console output being:

[<server.client_manager.ClientManager.Client object at 0x000001F87C2CCE80>, <server.client_manager.ClientManager.Client object at 0x000001F87C2CCD30>] Begin <server.client_manager.ClientManager.Client object at 0x000001F87C2CCE80> [<server.client_manager.ClientManager.Client object at 0x000001F87C2CCE80>, <server.client_manager.ClientManager.Client object at 0x000001F87C2CCD30>] end of unfollow user [<server.client_manager.ClientManager.Client object at 0x000001F87C2CCD30>] Passed Out of loop.

What am I doing wrong, here?

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
James Butler
  • 13
  • 1
  • 4

1 Answers1

1

This is an example of what you're doing, in a nutshell.

>>> x = [1,2]
>>> for i in x:
...     x.remove(2)
...     print("Hello world.")
...
Hello world.

When you use the for loop construct in python, you're calling next() on the iterator. The builtin iterator for lists behave like so when you are modifying the elements while iterating:

There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates.

You are decreasing the length and the iterator checks that. So it exits the loop after only 1 iteration.

If you wanted to run it through all elements, then perform a copy and assign that to your temp:

import copy
temp = copy.copy(x)
for i in temp:
    # Do whatever you want here
OneRaynyDay
  • 3,658
  • 2
  • 23
  • 56
  • I see. Is there any decent way to get around this? I assumed that temp = client.followedby would get around that. – James Butler Jun 13 '18 at 19:43
  • @JamesButler: `temp = client.followedby` is not a copy. If you want an actual copy, [there are options for that](https://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list). – user2357112 Jun 13 '18 at 19:45
  • You're not copying the object here. Check out the edit above. Hope that helps! – OneRaynyDay Jun 13 '18 at 19:46
  • 1
    @OneRaynyDay: It looks like a shallow copy is necessary here, not a deep copy. We want the original followers to unfollow the original user; we don't want to make copies and have them unfollow the user (or a copy of the user). Also, generators are a specific type of iterator. Iterating over a list does not involve a generator. – user2357112 Jun 13 '18 at 19:48
  • you can also `for i in x[:]` instead of deepcopy. :) – Srini Jun 13 '18 at 19:49
  • Good point @user2357112 on the shallow copy. I have corrected the answer above. – OneRaynyDay Jun 13 '18 at 19:50
  • `list` are not iterators. You can't call `next()` on a list. Lists are *iterables*, but they are not *iterators*, it's an important distinction – user3483203 Jun 13 '18 at 19:51
  • I think what you are trying to get at is what the for loop is doing behind the scenes, and you are correct about that, but it isn't quite clear here – user3483203 Jun 13 '18 at 19:58
  • @user3483203 Apologies for the phrasing, but I specifically said calling `next()` on the iterator, not the iterable, which is the list which contains the `__iter__` attribute. Iterator for lists is analogous to saying you call `iter([1,2,3])` to get an iterator object to call `next()` on. – OneRaynyDay Jun 13 '18 at 19:59