I'm trying to create a code that utilizes the __iter__() method as a generator, but I am getting an error saying:
TypeError: object() takes no parameters.
Additionally, I am unsure whether my yield function should be called within try: or within the main() function
I am fairly new to Python and coding, so any suggestions and advice would be greatly appreciated so that I can learn. Thanks!
class Counter(object):
def __init__(self, filename, characters):
self._characters = characters
self.index = -1
self.list = []
f = open(filename, 'r')
for word in f.read().split():
n = word.strip('!?.,;:()$%')
n_r = n.rstrip()
if len(n) == self._characters:
self.list.append(n)
def __iter(self):
return self
def next(self):
try:
self.index += 1
yield self.list[self.index]
except IndexError:
raise StopIteration
f.close()
if __name__ == "__main__":
for word in Counter('agency.txt', 11):
print "%s' " % word
Use yield for function __iter__:
class A(object):
def __init__(self, count):
self.count = count
def __iter__(self):
for i in range(self.count):
yield i
for i in A(10):
print i
In your case, __iter__ maybe looks something like this:
def __iter__(self):
for i in self.list:
yield i
You mistyped the declaration of the __init__ method, you typed:
def __init
Instead of:
def __init__
Related
Here's a simple iterator through the characters of a string.
class MyString:
def __init__(self,s):
self.s = s
self._ix = 0
def __iter__(self):
return self
def __next__(self):
try:
item = self.s[self._ix]
except IndexError:
self._ix = 0
raise StopIteration
self._ix += 1
return item
string = MyString('abcd')
iter1 = iter(string)
iter2 = iter(string)
print(next(iter1))
print(next(iter2))
Trying to get this iterator to function like it should. There are a few requirements. First, the __next__ method MUST raise StopIteration and multiple iterators running at the same time must not interact with each other.
I accomplished objective 1, but need help on objective 2. As of right now the output is:
'a'
'b'
When it should be:
'a'
'a'
Any advice would be appreciated.
Thank you!
MyString acts as its own iterator much like a file object
>>> f = open('deleteme', 'w')
>>> iter(f) is f
True
You use this pattern when you want all iterators to affect each other - in this case advancing through the lines of a file.
The other pattern is to use a separate class to iterate much like a list whose iterators are independent.
>>> l = [1, 2, 3]
>>> iter(l) is l
False
To do this, move the _ix indexer to a separate class that references MyString. Have MyString.__iter__ create an instance of the class. Now you have a separate indexer per iterator.
class MyString:
def __init__(self,s):
self.s = s
def __iter__(self):
return MyStringIter(self)
class MyStringIter:
def __init__(self, my_string):
self._ix = 0
self.my_string = my_string
def __iter__(self):
return self
def __next__(self):
try:
item = self.my_string.s[self._ix]
except IndexError:
raise StopIteration
self._ix += 1
return item
string = MyString('abcd')
iter1 = iter(string)
iter2 = iter(string)
print(next(iter1))
print(next(iter2))
Your question title asks how to get iterators, plural, to not communicate with each other, but you don't have multiple iterators, you only have one. If you want to be able to get distinct iterators from MyString, you can add a copy method:
class MyString:
def __init__(self,s):
self.s = s
self._ix = 0
def __iter__(self):
return self
def __next__(self):
try:
item = self.s[self._ix]
except IndexError:
self._ix = 0
raise StopIteration
self._ix += 1
return item
def copy(self):
return MyString(self.s)
string = MyString('abcd')
iter1 = string.copy()
iter2 = string.copy()
print(next(iter1))
print(next(iter2))
I have a list with string elements, and in the end I want to recieve:
a hello
b hello
c hello
d hello
And I've got this code:
list=['a','b','c','d']
class Iterator:
def __init__(self, start, end):
self.start=start
self.end=end
def __iter__(self):
return self
def __next__(self):
self.start += ' hello'
if self.start == list[-1]:
raise StopIterration
return self.start
if __name__ == '__main__':
for item in Iterator(list[0], list[-1]):
print(item)
However, the methond __next__ CANNOT MOVE FROM list[0] to list[1], and the python began to be crazy and add billion of "hello" to the list[0], and can't even stop the program, so it's the hell's loop now.
Problems are:
Adding billon of "hello" to list[0], not moving to list[1].
Doesn't finish the program at all, despite I wrote what is a condition for finish.
Your instance of Iterator isn't tied to the list at all; it's irrelevant that you used the list to create the instance; Iterator.__init__ only saw two string values.
__init__ needs a reference to the list itself for use by __next__. Further, hello is something you append to the return value of __next__, not something you need to append to internal state every time you call __next__.
list=['a','b','c','d']
class Iterator:
def __init__(self, lst):
self.lst = lst
self.start = 0
def __iter__(self):
return self
def __next__(self):
try:
value = self.lst[self.start]
except IndexError:
raise StopIteration
self.start += 1
return value + ' hello'
if __name__ == '__main__':
for item in Iterator(list):
print(item)
I implemented an iterator class as following:
import numpy as np
import time
class Data:
def __init__(self, filepath):
# Computationaly expensive
print("Computationally expensive")
time.sleep(10)
print("Done!")
def __iter__(self):
return self
def __next__(self):
return np.zeros((2,2)), np.zeros((2,2))
count = 0
for batch_x, batch_y in Data("hello.csv"):
print(batch_x, batch_y)
count = count + 1
if count > 5:
break
count = 0
for batch_x, batch_y in Data("hello.csv"):
print(batch_x, batch_y)
count = count + 1
if count > 5:
break
However the constructor is computationally expensive, and the for loop might be called multiple times. For example, in above code the constructor is called twice (each for loop create a new Data object).
How do I separate constructor and iterator? I am hoping to have the following code, where constructor is called once only:
data = Data(filepath)
for batch_x, batch_y in data.get_iterator():
print(batch_x, batch_y)
for batch_x, batch_y in data.get_iterator():
print(batch_x, batch_y)
You can just iterate over an iterable object directly, for..in doesn't require anything else:
data = Data(filepath)
for batch_x, batch_y in data:
print(batch_x, batch_y)
for batch_x, batch_y in data:
print(batch_x, batch_y)
That said, depending on how you implement __iter__(), this could be buggy.
E.g.:
Bad
class Data:
def __init__(self, filepath):
self._items = load_items(filepath)
self._i = 0
def __iter__(self): return self
def __next__(self):
if self._i >= len(self._items): # Or however you check if data is available
raise StopIteration
result = self._items[self._i]
self._i += 1
return result
Because then you couldn't iterate over the same object twice, as self._i would still point at the end of the loop.
Good-ish
class Data:
def __init__(self, filepath):
self._items = load_items(filepath)
def __iter__(self):
self._i = 0
return self
def __next__(self):
if self._i >= len(self._items):
raise StopIteration
result = self._items[self._i]
self._i += 1
return result
This resets the index every time you're about to iterate, fixing the above. This won't work if you're nesting iteration over the same object.
Better
To fix that, keep the iteration state in a separate iterator object:
class Data:
class Iter:
def __init__(self, data):
self._data = data
self._i = 0
def __next__(self):
if self._i >= len(self._data._items): # check for available data
raise StopIteration
result = self._data._items[self._i]
self._i = self._i + 1
def __init__(self, filepath):
self._items = load_items(filepath)
def __iter__(self):
return self.Iter(self)
This is the most flexible approach, but it's unnecessarily verbose if you can use either of the below ones.
Simple, using yield
If you use Python's generators, the language will take care of keeping track of iteration state for you, and it should do so correctly even when nesting loops:
class Data:
def __init__(self, filepath):
self._items= load_items(filepath)
def __iter__(self):
for it in self._items: # Or whatever is appropriate
yield return it
Simple, pass-through to underlying iterable
If the "computationally expensive" part is loading all the data into memory, you can just use the cached data directly.
class Data:
def __init__(self, filepath):
self._items = load_items(filepath)
def __iter__(self):
return iter(self._items)
Instead of creating a new instance of Data, create a second class IterData that contains an __init__ method that runs a process which is not as computationally expensive as instantiating Data. Then, create a classmethod in Data as an alternative constructor for IterData:
class IterData:
def __init__(self, filepath):
#only pass the necessary data
def __iter__(self):
#implement iter here
class Data:
def __init__(self, filepath):
# Computationaly expensive
#classmethod
def new_iter(cls, filepath):
return IterData(filepath)
results = Data.new_iter('path')
for batch_x, batch_y in results:
pass
This is what i currently have and says that it is missing one argument when it tries to push i
this is the class that I have for this code
class ArrayStack:
def __init__(self):
self._data = []
def __len__(self):
return len(self._data)
def is_empty(self):
return len(self._data) == 0
def push(self, a):
self._data.append(a)
def top(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._data[-1]
def pop(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._data.pop()
def reverselist():
expression = input("Enter whatever: ")
stacks = ArrayStack
listofstuff = []
for item in expression:
listofstuff.append(item)
print(listofstuff)
for token in listofstuff:
i = str(token)
stacks.push(i)
You need an instance of ArrayStack, not the class itself, change for ArrayStack(), this calls the constructor of your class.
def reverselist():
expression = input("Enter whatever: ")
stacks = ArrayStack()
listofstuff = []
for item in expression:
listofstuff.append(item)
print(listofstuff)
for token in listofstuff:
i = str(token)
stacks.push(i)
I'm creating an interator like so:
some_list = [1,2,5,12,30,75,180,440]
iter(some_list)
I have a need to access the current value of an iterator again. Is there a current() method that allows me to stay on the same position?
You certainly can make a class which will allow you to do this:
from collections import deque
class RepeatableIter(object):
def __init__(self,iterable):
self.iter = iter(iterable)
self.deque = deque([])
def __iter__(self):
return self
#define `next` and `__next__` for forward/backward compatability
def next(self):
if self.deque:
return self.deque.popleft()
else:
return next(self.iter)
__next__ = next
def requeue(self,what):
self.deque.append(what)
x = RepeatableIter([1, 2, 3, 4, 5, 6])
count = 0
for i in x:
print i
if i == 4 and count == 0:
count += 1
x.requeue(i)
The question is really why would you want to?
You can use numpy.nditer to build your iterator, then you have many amazing options including the current value.
import numpy
rng = range(100)
itr = numpy.nditer([rng])
print itr.next() #0
print itr.next() #1
print itr.next() #2
print itr.value #2 : current iterator value
Adapting the third example from this answer:
class CurrentIterator():
_sentinal = object()
_current = _sentinal
#property
def current(self):
if self._current is self._sentinal:
raise ValueError('No current value')
return self._current
def __init__(self, iterable):
self.it = iter(iterable)
def __iter__(self):
return self
def __next__(self):
try:
self._current = current = next(self.it)
except StopIteration:
self._current = self._sentinal
raise
return current
next = __next__ # for python2.7 compatibility
Some interesting points:
use of _sentinal so an error can be raised if no current value exists
use of property so current looks like a simple attribute
use of __next__ and next = __next__ for Python 2&3 compatibility