Sorry if this is a silly question, but I could not make my mind up how it could work.
I defined an iterator which has a structure like that (it is a bit more complicated, but the model will do the job):
class MyIterator ():
def __init__(self):
print ('nothing happening here')
def __iter__ (self):
self.a_list=[x for x in range (10)]
for y in a_list:
print(y)
def __next__ (self):
self.a_list = [x+1 for x in self.a_list]
for y in a_list:
print (y)
But how can I loop over it? Do I always have to call the methods manually? Or am I simply using the wrong tool?
One of the problems is that you are mixing two concepts: And
iterable defines an __iter__() method that returns an iterator,
but no __next__() method. An iterator in turn defines a
__next__() method, and a trivial __iter__() implementation that
returns self. Something like this:
class Iterable(object):
def __iter__(self):
return Iterator()
class Iterator(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
result = self.i
self.i += 1
return result
An alternative is to define the __iter__() method of the iterable as a generator function:
class Iterable(object):
def __iter__(self):
i = 0
while True:
yield i
i += 1
Related
I want to define a class method which applies a different class method to a list - I thought the below would work but it doesn't seem to, am I missing something obvious?
class Foo():
def __init__(self):
pass
def method1(self, data):
do some stuff
def method2(self, iterable):
map(self.method1, iterable)
do some more stuff
As a concrete example, method2 is applying method1 to each element of a list but the actual content of method1 (printing) doesn't seem to be executing:
class Foo():
def __init__(self):
self.factor = 2
def method1(self, num):
print(num*self.factor)
def method2(self, ls):
map(self.method1, ls)
f = Foo()
f.method2([1,2,3])
I would expect this to print 2, 4, 6 but nothing is printed.
map returns a generator (in Python 3), so you still need to iterate over it:
def method2(self, iterable):
for val in map(self.method1, iterable):
do some stuff with val
If you don't care about the return values, you could always just wrap it in a list: list(map(self.method1, iterable)).
Let's say I want to implement some list class in python with extra structure, like a new constructor. I wrote:
import random
class Lis(list):
def __init__(self, n):
self = []
for i in range(n):
self.append(random.randint(0, 30))
Now doing Lis(3) gives me an empty list. I don't know where I did it wrong.
You are overriding the object with self = []
try the following
import random
class Lis(list):
def __init__(self, n):
for i in range(n):
self.append(random.randint(0, 30))
if you want to return a list from object call
class MyClass(object):
def __init__(self):
print "never called in this case"
def __new__(cls):
return [1,2,3]
obj = MyClass()
print(obj)
How to return a value from __init__ in Python?
or if you want an modified object try:
class MyClass:
def __getitem__(self, key):
not_self = []
for i in range(n):
not_self.append(random.randint(0, 30))
return not_self
myobj = MyClass()
myobj[3] #Output: 6
How to override the [] operator in Python?
I am not sure if using self like you did is healthy.
In python, I have a class with a method that returns a generator:
class foo():
data = [1, 2, 3]
def mygen(self):
for d in self.data:
yield d
instance = foo()
print([i for i in instance.mygen()])
But I can't reverse this:
print([i for i in reversed(instance.mygen())])
TypeError: 'generator' object is not reversible
So I thought I could implement a class which returns a generator when calling __iter__, like this
class foo():
data = [1, 2, 3]
def mygen(self):
return _ReversibleIterator(self)
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
def __iter__(self):
for d in obj.data:
yield d
def __reversed__(self):
for d in reversed(obj.data):
yield d
But I think this isn't quite the same, because the _ReversibleIterator class doesn't have a next() method.
So what is the pythonic way to create a class method that returns an iterator that can be reversed()?
(Obviously I'm just using [1,2,3] as an example. The real thing to iterate over is less trivially reversible)
According to the docs, reversed must have one of two things to work with: a __reversed__ method OR a __len__ and a __getitem__ method. If you think about it, this makes sense because most generators can't support reversed because they generate results on the fly: they don't know what the next, let alone the last element is going to be. However, if you know its length and have random-access to any index, it can be reversed.
class foo():
data = [1, 2, 3]
def mygen(self):
return _ReversibleIterator(self)
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
self.index = 0
def __iter__(self):
self.index = 0
return self
def __reversed__(self):
return reversed(self.obj.data)
def __next__(self):
try:
el = self.obj.data[self.index]
except IndexError:
raise StopIteration
self.index += 1
return el
or
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
self.index = 0
def __iter__(self):
self.index = 0
return self
def __len__(self):
return len(self.obj.data)
def __getitem__(self, i):
return self.obj.data[i]
def __next__(self):
try:
el = self[self.index]
except IndexError:
raise StopIteration
self.index += 1
return el
By the way, if you would like, you can replace for d in whatever: yield d with yield from whatever.
Here is part of my code:
some_item = target_item
first_item = my_list[0]
print first_item
print some_item
print my_list.index(first_item)
print my_list.index(some_item)
print "Finished"
exit()
And here is what I get:
<models.adb.ADB object at 0x7f1ec7654b50>
<models.seb.SEB object at 0x7f1ec7654b90>
0
0
Finished
My items in my_list are class objects.
Could someone please explain this behavior?
It depends on the comparison implemented for ADB and SEB, probably by implementing the __eq__ operator for this classes.
When python will look for the instance of first_item and some_item on the list, he will use this operator to determine if the i-th element on the list is this element, sweeping for every i.
Therefore, if the implementation is like
class Parent:
def __init__(self):
self.x = 0
def __eq__(parentObj):
return self.x == parentObj.x
class ADB (Parent):
def __init__(self): super.__init__()
class SEB (Parent):
def __init__(self): super.__init__()
it would result in ADB() == SEB(), therefore, causing the objects to both "match" the first list's element when being compared to it, but being different objects at the same time.
Note that same can go for this as well, used inheritance for simplification of the example.
class ADB (Parent):
def __init__(self):
self.x = 0
def __eq__(obj):
return self.x == obj.x
class SEB (Parent):
def __init__(self):
self.x = 0
def __eq__(obj):
return self.x == obj.x
Just curious,
Is there any difference (advantages and disadvantages) between using len() or def __len__() when I build a class? And which is the best Python style?
class foo(object):
def __init__(self,obs=[])
self.data = obs
self.max = max(obs)
self.min = min(obs)
self.len = len(obs)
or
class foo(object):
def __init__(self,obs=[])
self.data = obs
self.max = max(obs)
self.min = min(obs)
def __len__(self):
return len(self.data)
There is a huge difference.
The __len__() method is a hook method. The len() function will use the __len__ method if present to query your object for it's length.
The normal API people expect to use is the len() method, using a .len attribute instead would deviate from that norm.
If the length of self.data is not expected to change, you can always cache the length in an attribute and have .__len__() return that attribute.
class foo(object):
def __init__(self, obs=None):
if obs is None: # provide a default if no list was passed in.
obs = []
self.data = obs
self.max = max(obs)
self.min = min(obs)
self._data_len = len(obs)
def __len__(self):
return self._data_len
There are several differences:
Only the second approach will give you the familiar len(obj) syntax for foo. The first will require obj.len().
If the length of self.data can change post-construction, only the second version will reflect the new length.