Python class and __iter__ - python

What are the benefits of using the __iter__ function in a Python class?
In the code below I am just setting up two simple classes. The first class takes in a list as an argument, and I am able to loop over this list without using the __iter__ function. The second bit of code uses the __iter__ function to loop over a list.
What is the benefit of using __iter__ when there are already ways of looping over stuff in a class?
EG 1: no __iter__
class test_class:
def __init__(self, list):
self.container_list = list
def print (self):
a = self.container_list
return a
test_list = test_class([1,2,3,4,5,6,7])
x = test_class.print(test_list)
for i in x:
print (i)
EG 2: yes __iter__
class list_using_iter:
def __init__(self):
self.list = [1,2,3,4]
self.index = -1
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index == len(self.list):
raise StopIteration
return self.list [self.index]
r = list_using_iter()
itr = iter(r)
print(next(itr))
print(next(itr))
print(next(itr))
print(next(itr))
print(next(itr)) # Raises the exception!

Your first example is not iterable, but contains an attribute that is. Your second example is iterable, but you iterate simply by "following" another iterable. Here's an example of a iterable that does more work itself:
import itertools
class Fibs:
def __init__(self, a, b):
self.a = a
self.b = b
def __iter__(self):
a = self.a
b = self.b
while True:
yield a
a, b = b, a + b
real_fibs = Fibs(0,1)
for i in itertools.islice(real_fibs, 10):
print(i)
Fibs.__iter__ isn't simply regurgitating values obtained from some other value's __iter__ method; it is computing and yielding new values on demand.
Actually, the preceding is an example of a class that knows how to create its own iterator, rather than having each object be iterable. Here's a version that defines next itself.
class Fibs:
def __init__(self, a, b):
self.a = a
self.b = b
def __iter__(self):
return self
def __next__(self):
rv = self.a
self.a, self.b = self.b, self.a + self.b
return rv

In both cases, the looping works because of __iter__. In your first example, your print function returns a loop.
The implementation of the for keyword will call __iter__ (or the corresponding slot within the C implementation since the code involved is in the C interpreter) in order to loop over the list.
In your second example you could have written
for elt in r:
print(elt)
which would have internally called __iter__ to implement the for loop.
In general you tend to use for rather than iter and next directly. The cases where you use iter and next directly are when you're producing a callback function that will produce an iterator or when you're defining one iterator in terms of another.
In terms of when should you write your own __iter__ or return some object that does its own iteration, that all depends on what functionality you want. For example, your first class is more powerful because two people can be iterating the list at the same time. In your second class, because you store the index in the class itself, only one person can successfully use the iterator at a time.
However, if you had complex enough behavior, the second approach where you define your own __iter__ might make sense.

Related

Decorate a class with Python that prints the int variables of the decorated class

I'm studying for a python course and one of the exercise was to create a decorator for this class that returns every int variables.`
#decoratoreDiClasse
class MyClass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 'w'`
My problem is that the list is always empty beacuse dict does not return the variables inside init,how can i solve my problem?
i've written my decorator below
def decoratoreDiClasse(cls):
def elencaVariabili():
lista = []
print(cls)
lista1 = cls.__dict__
print(lista1)
for ab in lista1:
if isinstance(ab, int):
lista.append(ab)
return lista
setattr(cls, "elencaVariabili", elencaVariabili())
return cls
here's the part of the main that should print the variables,I cannot change anything apart from "decoratoreDiClasse" due to the teacher request.
for v in x.elencaVariabili():
print(v, end=' ')
It looks like you're supposed to have your decorator add a method to the class that prints out integer-valued attributes on an instance it's called on. That's not what you're currently trying to do, as your code tries to find the variables on the class instead of on an instance later on.
Think of what you're doing as a method, and it will be a lot simpler:
def decoratoreDiClasse(cls):
def elencaVariabili(self): # this is a method, so it should take self!
lista = [value for value in self.__dict__.values() # loop over our attribute values
if isinstance(value, int)] # and pick out the integers!
return lista
setattr(cls, "elencaVariabili", elencaVariabili) # don't call the method here
return cls
It's not entirely clear from your code if you're supposed to be returning the names of the integer variables or just the values themselves. I went with just the values, but if you need the variable names, you may need to change the list comprehension to iterate over the items() of the instance's dictionary rather than just the values().

Counting the occurence of instances of a certain class in list of lists

I have list of lists in which I want to count the number of B() and C() instances and am looking for a suitable method to do this. Using collections.Counter() and the .count() function have resulted in strange results, and I suspect I do not fully understand how list of lists work in python, or how lists of class instances work in python.
This is the list of lists:
lst = [[B() for w in range(x)] for h in range(y)]
with
class A():
def __init__(self, name):
self.name = name
class B(A):
def __init__(self, name = "B"):
A.__init__(self, name)
def update(self):
if random.random() < 0.05:
return C()
else: return self
class C(A):
def __init__(self, name = "C"):
A.__init__(self, name)
And, I use the below code to randomly change B() instances in lst into C() instances:
for row in range(y):
for column in range(x):
lst[row][column] = lst[row][column].update()
How do I count the number of B() and C() instances in the list?
You can use isinstance()
You can check what class an element is with isinstance().
Here is an example:
>>> a = C()
>>> isinstance(a, C)
True
So if you have your list, you can do:
occurrences_of_B = sum(isinstance(i, B) for r in list for i in r)
occurrences_of_C = sum(isinstance(i, C) for r in list for i in r)
you can get the occurrences of the B() and C() classes.
Essentially, we are using a generator comprehension to apply the isinstance() function to every element in the list. We then use sum on the generator as True evaluates to 1 and False to 0, so we will get the total count.
As a side note, although I said it is not good practice to name a list 'array', it is actually worse to name it exactly 'list' as this prevents you from being able to use the list() function! Better would probably be lst or l. :)

Equivalent code of __getitem__ in __iter__

I am trying to understand more about __iter__ in Python 3. For some reason __getitem__ is better understood by me than __iter__. I think I get somehow don't get the corresponding next implemention followed with __iter__.
I have this following code:
class Item:
def __getitem__(self,pos):
return range(0,30,10)[pos]
item1= Item()
print (f[1]) # 10
for i in item1:
print (i) # 0 10 20
I understand the code above, but then again how do i write the equivalent code using __iter__ and __next__() ?
class Item:
def __iter__(self):
return self
#Lost here
def __next__(self,pos):
#Lost here
I understand when python sees a __getitem__ method, it tries iterating over that object by calling the method with the integer index starting with 0.
In general, a really good approach is to make __iter__ a generator by yielding values. This might be less intuitive but it is straight-forward; you just yield back the results you want and __next__ is then provided automatically for you:
class Item:
def __iter__(self):
for item in range(0, 30, 10):
yield item
This just uses the power of yield to get the desired effect, when Python calls __iter__ on your object, it expects back an iterator (i.e an object that supports __next__ calls), a generator does just that, producing each item as defined in your generator function (i.e __iter__ in this case) when __next__ is called:
>>> i = iter(Item())
>>> print(i) # generator, supports __next__
<generator object __iter__ at 0x7f6aeaf9e6d0>
>>> next(i)
0
>>> next(i)
10
>>> next(i)
20
Now you get the same effect as __getitem__. The difference is that no index is passed in, you have to manually loop through it in order to yield the result:
>>> for i in Item():
... print(i)
0
10
20
Apart from this, there's two other alternatives for creating an object that supports Iteration.
One time looping: Make item an iterator
Make Item an iterator by defining __next__ and returning self from __iter__ in this case, since you're not using yield the __iter__ method returns self and __next__ handles the logic of returning values:
class Item:
def __init__(self):
self.val = 0
def __iter__(self):
return self
def __next__(self):
if self.val > 2: raise StopIteration
res = range(0, 30, 10)[self.val]
self.val += 1
return res
This also uses an auxiliary val to get the result from the range and check if we should still be iterating (if not, we raise StopIteration):
>>> for i in Item():
... print(i)
0
10
20
The problem with this approach is that it is a one time ride, after iterating once, the self.val points to 3 and iteration can't be performed again. (using yield resolves this issue). (Yes, you could go and set val to 0 but that's just being sneaky.)
Many times looping: create custom iterator object.
The second approach is to use a custom iterator object specifically for your Item class and return it from Item.__iter__ instead of self:
class Item:
def __iter__(self):
return IterItem()
class IterItem:
def __init__(self):
self.val = 0
def __iter__(self):
return self
def __next__(self):
if self.val > 2: raise StopIteration
res = range(0, 30, 10)[self.val]
self.val += 1
return res
Now every time you iterate a new custom iterator is supplied and you can support multiple iterations over Item objects.
Iter returns a iterator, mainly a generator as #machineyearning told at the comments, with next you can iterate over the object, see the example:
class Item:
def __init__(self):
self.elems = range(10)
self.current = 0
def __iter__(self):
return (x for x in self.elems)
def __next__(self):
if self.current >= len(self.elems):
self.current = 0
raise StopIteration
return self.elems[self.current]
>>> i = Item()
>>> a = iter(i)
>>> for x in a:
... print x
...
0
1
2
3
4
5
6
7
8
9
>>> for x in i:
... print x
...
0
1
2
3
4
5
6
7
8
9

How to implement "next" for a dictionary object to be iterable?

I've got the following wrapper for a dictionary:
class MyDict:
def __init__(self):
self.container = {}
def __setitem__(self, key, value):
self.container[key] = value
def __getitem__(self, key):
return self.container[key]
def __iter__(self):
return self
def next(self):
pass
dic = MyDict()
dic['a'] = 1
dic['b'] = 2
for key in dic:
print key
My problem is that I don't know how to implement the next method to make MyDict iterable. Any advice would be appreciated.
Dictionaries are themselves not an iterator (which can only be iterated over once). You usually make them an iterable, an object for which you can produce multiple iterators instead.
Drop the next method altogether, and have __iter__ return an iterable object each time it is called. That can be as simple as just returning an iterator for self.container:
def __iter__(self):
return iter(self.container)
If you must make your class an iterator, you'll have to somehow track a current iteration position and raise StopIteration once you reach the 'end'. A naive implementation could be to store the iter(self.container) object on self the first time __iter__ is called:
def __iter__(self):
return self
def next(self):
if not hasattr(self, '_iter'):
self._iter = iter(self.container)
return next(self._iter)
at which point the iter(self.container) object takes care of tracking iteration position for you, and will raise StopIteration when the end is reached. It'll also raise an exception if the underlying dictionary was altered (had keys added or deleted) and iteration order has been broken.
Another way to do this would be to just store in integer position and index into list(self.container) each time, and simply ignore the fact that insertion or deletion can alter the iteration order of a dictionary:
_iter_index = 0
def __iter__(self):
return self
def next(self):
idx = self._iter_index
if idx is None or idx >= len(self.container):
# once we reach the end, all iteration is done, end of.
self._iter_index = None
raise StopIteration()
value = list(self.container)[idx]
self._iter_index = idx + 1
return value
In both cases your object is then an iterator that can only be iterated over once. Once you reach the end, you can't restart it again.
If you want to be able to use your dict-like object inside nested loops, for example, or any other application that requires multiple iterations over the same object, then you need to implement an __iter__ method that returns a newly-created iterator object.
Python's iterable objects all do this:
>>> [1, 2, 3].__iter__()
<listiterator object at 0x7f67146e53d0>
>>> iter([1, 2, 3]) # A simpler equivalent
<listiterator object at 0x7f67146e5390>
The simplest thing for your objects' __iter__ method to do would be to return an iterator on the underlying dict, like this:
def __iter__(self):
return iter(self.container)
For more detail than you probably will ever require, see this Github repository.

How exactly python converts arbitrary object into list?

Documentation says:
The constructor builds a list whose items are the same and in the same
order as iterable‘s items. iterable may be either a sequence, a
container that supports iteration, or an iterator object. If iterable
is already a list, a copy is made and returned, similar to
iterable[:]...
But if I have an object a of my class A, that implements __iter__, __len__ and __getitem__, which interface is used by list(a) to iterate my object and what logic is behind this?
My quick experimenting confuses me:
class A(object):
def __iter__(self):
print '__iter__ was called'
return iter([1,2,3])
def __len__(self):
print '__len__ was called'
return 3
def __getitem__(self, index):
print '__getitem(%i)__ was called' % index
return index+1
a = A()
list(a)
Outputs
__iter__ was called
__len__ was called
[1, 2, 3]
A.__iter__ was called first, ok. But why then A.__len__ was called? And then why A.__getitem__ was not called?
Then I turned __iter__ to a generator
And this changed the order of magic method calls!
class B(object):
def __iter__(self):
print '__iter__ was called'
yield 1
yield 2
yield 3
def __len__(self):
print '__len__ was called'
return 3
def __getitem__(self, index):
print '__getitem(%i)__ was called' % index
return index+1
b = B()
list(b)
Outputs
__len__ was called
__iter__ was called
[1, 2, 3]
Why B.__len__ was called first now? But why then B.__getitem__ was not called, and conversion was done with B.__iter__?
And what confuses me most is why the order of calls of __len__ and __iter__ is different in cases of A and B?
The call order didn't change. __iter__ still got called first, but calling __iter__ doesn't run the function body immediately when __iter__ is a generator. The print only happens once next gets called.
__len__ getting called is an implementation detail. Python wants a hint for how much space to allocate for the list, so it calls _PyObject_LengthHint on your object, which uses len if the object supports it. It is expected that calling len on an object will generally be fast and free of visible side effects.

Categories