Python iterable collection that wraps a dictionary - python

I'm trying to modify the following code, so that MyCollection will wrap a dictionary. I still have to implement the iter and next methods in order to have the "for element in collection" functionality. I know that can be easily done by iterating through the values, but I am required to do it like this. Can someone help me ?
class MyCollection:
def __init__(self):
self._data = [] // should be {}
def __iter__(self):
'''
Return an iterator
'''
self._iterPoz = 0
return self
def __next__(self):
'''
Returns the next element of the iteration
'''
if self._iterPoz >= len(self._data):
raise StopIteration()
rez = self._data[self._iterPoz]
self._iterPoz = self._iterPoz + 1
return rez

This begins with a design decision. When you iterate MyCollection what data do you want? If its the values of the contained dictionary you can return its iterator and then you don't implement __next__ at all.
class MyCollection:
def __init__(self):
self._data = {}
def __iter__(self):
'''
Return an iterator of contained values
'''
return iter(self._data.values())

Related

Second level looping over dictionaries

In building a class with an out line like below I would like the behaviour of the for loops to, if done once: just give the keys as normal an then move on to the next line of code. But if a second loop is set up inside the first loop it would give the keys on the first loop and then ea value in the sequences in the second loop. The problem I can't figure out is how to set up this under iter.
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
def __iter__(self):
pass # ???????
Something like this:
dct = dict(container1=[5,6,7,8], container2=('a','b','c')
if one loop is used:
for ea in dct:
print(ea)
print("Howdy")
'containter1'
'containter2'
Howdy
If a nest loop is used:
for ea in dct:
print(ea)
for i in dct.get(ea):
print(i)
'container1'
5
6
...
'container2'
a
b
c
To answer your immediate question, you could just copy how dictionaries implement dict.get and dict.__iter__:
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
def __iter__(self):
for attr in dir(self):
if not attr.startswith('_') and attr != 'get':
yield attr
def get(self, key):
return getattr(self, key)
It's not a very good approach, however. Looking at the attributes of your object at runtime isn't a good idea, because it will break when you subclass and it will add needless complexity. Instead, just use a dictionary internally:
class MyClass():
def __init__(self):
self.container = {
'cont1': [1, 2, 3, 4],
'cont2': ('a', 'b', 'c')
}
def __iter__(self):
return iter(self.container)
def get(self, key):
return self.container.get(key)
You can do this with a second class like this
class MyClass():
def __init__(self):
self.data = [MyClass2({'cont1' : [1,2,3,4]}),MyClass2({'cont2' : ('a','b','c')})]
def __iter__(self):
for item in self.data:
yield item
class MyClass2():
def __init__(self, mydict):
self.d = mydict
def __iter__(self):
for item in self.d.values():
for value in item:
yield value
def __repr__(self):
return(list(self.d.keys())[0])
m = MyClass()
for k in m:
print(k)
for val in k:
print(val)
You cannot do that simply by implementing __iter__. __iter__ should return an iterator, that is, an object that keeps the state of an iteration (the current position in a sequence of items) and has a method next that returns with each invocation the next item in the sequence.
If your object has nested sequences you can implement an iterator that will traverse only the external sequence, or one that will traverse both
the external and the internal sequences - in a depth-first or a breath-first fashion - but it does not make sense to use nested loops on the same iterable:
# iterate over every item in myobj
for x in myobj:
...
# iterate over every item again? not likely what you want!
for y in myobj:
A more likely situation is:
for x in myob:
...
for y in x:
...
How would you feel about this:
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
self.conts = {'container1':self.cont1, 'container2':self.cont2}
def __iter__(self):
return self.conts.iteritems()
dct = MyClass()
print('One loop')
for mi in dct:
print(mi)
print('='*40)
print('Nested loops')
for name, values in dct:
print(name)
for i in values:
print(i)
Which outputs:
One loop
container1
container2
========================================
Nested loops
container1
1
2
3
4
container2
a
b
c
Update
I don't know that I would really recommend this, but this seems to more closely fit what the OP wants:
class MyIterator(object):
def __init__(self, name, values):
self.vals = iter(values)
self.name = name
def __iter__(self):
return self.vals
def __str__(self):
return self.name
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
self.conts = [MyIterator('container1', self.cont1),
MyIterator('container2', self.cont2)]
def __iter__(self):
return iter(self.conts)
dct = MyClass()
for mi in dct:
print(mi)
for i in mi:
print(i)
This is the only way I can think of to be able to print the name and then iterate over it as the values list. This works by overriding the __str__ method to change how the object gets "stringified". But as I said earlier, I think you would be better served with the first part of the answer.
Sorry, just realized nauer's answer already showed something like this.

len() function not working properly in Python

I have the following code:
class Stat(list):
def __init__(self, lst = []):
self.s = list(lst)
def __repr__(self):
return "Stat({})".format(self.s)
def add(self, item):
self.s.append(item)
def len(self):
return len(self.s)
...(more methods, but not necessary)
All of the methods work properly but len(). No matter the length of the Stat object, the returned length is always 0; I don't understand why.
it will return 0 always when you are using it like this:
x = Stat([1,3,4,5])
print len(x)
if you want to override len function use this code:
def __len__(self):
return len(self.s)
s = Stat([1, 2])
s.add(1)
s.add(2)
print s.len()
I have run your code, the result is correct in my environment.
Override the magic method __len__(self) to control the output of a call to len(my_stat_object):
class Stat(list):
def __init__(self, lst = []):
self.s = list(lst)
def __repr__(self):
return "Stat({})".format(self.s)
def add(self, item):
self.s.append(item)
def __len__(self):
return len(self.s)
If what you're trying to run is len(stat) and not stat.len(), your function len should be named __len__ instead. Here's the docs: object.len
stat = Stat([1, 2])
len(s) # 0 if len, 2 if __len__
As a side note, you might want to replace lst=[] in your init definition, as it can cause some weird looking behaviours. Read about it here: mutable default argument

__iter__ (iterator and generator)

I read a book about advanced topics in python.
The author was trying to explain the generator.
This was his example to explain:
class rev:
def __init__(self,data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
def main():
reve = rev('zix')
for i in reve:
print(i)
if __name__=='__main__':
main()
The main idea of this code is to reverse generators.
The output is :
x
i
z
The thing I found hard to understand is this part:
def __iter__(self):
return self
Can someone explain it to me?
When you do for x in xs, xs has to be an iterable, which means you can get an iterator out of it by iter(xs), which you can do when xs.__iter__() is implemented. An iterator is required to implement __next__(), so that the in operator can consume it one by one by calling next() on it.
Now, in your case
reve = rev("hello") # is an iterable, as there is rev.__iter__()
rev1 = iter(reve) # is an iterator, this is what rev.__iter__() returns
rev2 = next(rev1) # now go on calling next() till you get StopIteration
Type the above snippet in REPL. Run it a few times. You will get a feel for it.
The iterator protocol is comprised of two methods:
__iter__, and
__next__
Also, a requirement is that __iter__ returns self -- so if you have an obj that is an iterator then
obj is iter(obj) is obj.__iter__()
is true.
This is a good thing because it allows us to say iter = iter(obj) and if obj was already an iterator we still have the same object.
Since your class provides an implementation for next() it returns self so the caller will call that when looping over your object. In contrast, if you only wanted to wrap a data structure that already provides an implementation of __iter__ and next (e.g. list), you could return self.data.__iter__() from your class. In that case the caller would call next() defined on that object when doing a loop or list comprehension let's say.
class rev:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
class rev2:
def __init__(self, data):
self.data = data
def __iter__(self):
return self.data.__iter__()
def main():
for i in rev('zix'): # str doesn't provide implementation for __iter__
print(i)
for i in rev2(['z', 'i', 'x']): # list does so no need to implement next
print(i)

Implicitly binding callable objects to instances

I have this code:
class LFSeq: # lazy infinite sequence with new elements from func
def __init__(self, func):
self.evaluated = []
self.func = func
class __iter__:
def __init__(self, seq):
self.index = 0
self.seq = seq
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
And I explicitely want that LFSeq.__iter__ becomes bounded to an instance of LFSeq like any other user-defined function would have been.
It doesn't work this way though because only user-defined functions are bounded and not classes.
When I introduce a function decorator like
def bound(f):
def dummy(*args, **kwargs):
return f(*args, **kwargs)
return dummy
then I can decorate __iter__ by it and it works:
...
#bound
class __iter__:
...
This feels somehow hacky and inconsistent however. Is there any other way? Should it be that way?
I guess yes because otherwise LFSeq.__iter__ and LFSeq(None).__iter__ wouldn't be the same object anymore (i.e. the class object). Maybe the whole thing about bounded functions should have been syntactic sugar instead of having it in the runtime. But then, on the other side, syntactic sugar shouldn't really dependent on content. I guess there has to be some tradeoff at some place.
The easiest solution for what you are trying to do is to define your __iter__() method as a generator function:
class LFSeq(object):
def __init__(self, func):
self.evaluated = []
self.func = func
def __iter__(self):
index = 0
while True:
if index == len(self.evaluated):
self.evaluated.append(self.func())
yield self.evaluated[index]
index += 1
Your approach would have to deal with lots of subtleties of the Python object model, and there's no reason to go that route.
In my opinion, the best solution is #Sven one, no doubt about it. That said, what you are trying to do really seems extremely hackish - I mean, to define __iter__ as a class. It will not work because declaring a class inside another one is not like defining a method, but instead it is like defining an attribute. The code
class LFSeq:
class __iter__:
roughly equivalent to an attribution that will create a class field:
class LFSeq:
__iter__ = type('__iter__', (), ...)
Then, every time you define an attribute inside a class, this is bound to the class itself, not to specific instances.
I think you should follow #Sven solution, but if you really want to define a class for any other reason, it seems you are lucky, because your generator class does not depend upon nothing from the LFSeq instance itself. Just define the iterator class outside:
class Iterator(object):
def __init__(self, seq):
self.index = 0
self.seq = seq
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
and instantiate it inside LFSeq.__iter__() method:
class LFSeq(object): # lazy infinite sequence with new elements from func
def __init__(self, func):
self.evaluated = []
self.func = func
def __iter__(self):
return Iterator(self)
If you eventually need to bind the iterator class to the instance, you can define the iterator class inside LFSeq.__init__(), put it on a self attribute and instantiate it in LFSeq.__iter__():
class LFSeq(object): # lazy infinite sequence with new elements from func
def __init__(self, func):
lfseq_self = self # For using inside the iterator class
class Iterator(object): # Iterator class defined inside __init__
def __init__(self):
self.index = 0
self.seq = lfseq_self # using the outside self
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
self.iterator_class = Iterator # setting the itrator
self.evaluated = []
self.func = func
def __iter__(self):
return self.iterator_class() # Creating an iterator
As I have said, however, #Sven solution seems finer. I just answered do try to explain why your code did not behaved as you expected and to provide some info about to do what you want to do - which may be useful sometimes nonetheless.

How does this class implement the "__iter__" method without implementing "next"?

I have the following code in django.template:
class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
try:
template_string = smart_unicode(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
if settings.TEMPLATE_DEBUG and origin is None:
origin = StringOrigin(template_string)
self.nodelist = compile_string(template_string, origin)
self.name = name
def __iter__(self):
for node in self.nodelist:
for subnode in node:
yield subnode
def render(self, context):
"Display stage -- can be called many times"
return self.nodelist.render(context)
The part I am confused about is below. How does this __iter__ method work? I can't find any corresponding next method.
def __iter__(self):
for node in self.nodelist:
for subnode in node:
yield subnode
This is the only way that I know how to implement __iter__:
class a(object):
def __init__(self,x=10):
self.x = x
def __iter__(self):
return self
def next(self):
if self.x > 0:
self.x-=1
return self.x
else:
raise StopIteration
ainst = a()
for item in aisnt:
print item
In your answers, try to use code examples rather than text, because my English is not very good.
From the docs:
If a container object’s __iter__()
method is implemented as a generator,
it will automatically return an
iterator object (technically, a
generator object) supplying the
__iter__() and __next__() methods.
Here is your provided example using a generator:
class A():
def __init__(self, x=10):
self.x = x
def __iter__(self):
for i in reversed(range(self.x)):
yield i
a = A()
for item in a:
print(item)
That __iter__method returns a python generator (see the documentation), as it uses the yield keyword.
The generator will provide the next() method automatically; quoting the documentation:
What makes generators so compact is that the __iter__() and next() methods are created
automatically.
EDIT:
Generators are really useful. If you are not familiar with them, I suggest you readup on them, and play around with some test code.
Here is some more info on iterators and generators from StackOverflow.

Categories

Resources