Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling iter() on an instance of my class doesn't create a 'snapshot' of its current values?

So I've created a custom class in Python that supports iteration using the __iter__ method. My __iter__ method is structured as a generator object, using yield to give each of the values in the instance of the class.

I'm finding that if I say something like x = iter(my_instance) and then edit the data stored in my instance, if I subsequently build a list out of x, I will find that that list contains the values that my_class currently has, rather than the values it had when I called iter().

So my question is two-fold at this point. First of all, is that what is supposed to happen? If so, why does it work like that?


Above is the main thing, but if you really want to go above and beyond, keep reading. Now the reason I'm asking this question is that I've had to create this class as an assignment at university. My professor has provided a program that will run a bunch of tests on this class I created in order to give me a good idea of whether or not I've done it correctly the first time.

My program is passing every single check except the last one, which basically does what is described above. The problem is that it expects the list that is built from a previous call to iter(my_object) to reflect only values stored in my_object at the time that iter() was called on it.

My initial reaction after looking this over was that my professor intended to get a result similar to simply calling list(my_object), but I'm the student here, so it seems much more likely I simply coded my __iter__ method in some very strange way, which is causing this behavior. Or did Python change how the iter() function works recently?


I would prefer not to post my code, since it's for a class, and that could technically be considered cheating (I know that's stupid and unhelpful, sorry). However, I can link the specification, which is right here (specifically part 1, the Bag class).

like image 226
LandGod Avatar asked Oct 18 '22 11:10

LandGod


2 Answers

As (I think) @Cyb3rFly3r was trying to say, the behavior of iterating over a mutable class that does this is highly discordant with the way it works for the container classes in the standard library — and would therefore be a somewhat debatable design choice to make on those grounds alone in the real world.

It also runs counter to probably one of the main reasons the concept of iterators was introduced into Python, which was to make creating separate copies of the contents of container objects so they could be iterated-over unnecessary.

Anyway, that's exactly what you'll need to do in your __iter__() method (and sounds like what is in the hint from Detail 11 of the linked specification shown in Peter Gibson's answer is suggesting, copying the dictionary that stores all the information in a Bag instance — be it a plain dictionary, a defaultdict, or whatever).

You could optimize things (by minimizing the extra memory required for the copy and the ease of internally iterating of it) by creating an internal list of its contents rather than making a copy of the whole likely more-complex data structure. **Tip**: Doing this would probably be very similar to what you're going to need to do to implement the __repr__() method so its results look like:

    Bag(['a', 'c', 'b', 'b', 'd', 'd', 'd'])

as shown in Detail 3 of the specification.

Since you've chosen not to post any code, it difficult to help you much further.

like image 178
martineau Avatar answered Oct 30 '22 02:10

martineau


The assignment appears to give you a strong hint as to how you should go about this

Ensure that the iterator produces those values in the Bag at the time the iterator starts executing; so mutating the Bag during iteration will not affect what values it produces.

Hint: Write this method as a call to a local generator, passing a copy of the dictionary (covered in Friday's lecture in Week 4).

like image 35
Peter Gibson Avatar answered Oct 30 '22 02:10

Peter Gibson