I wrote a generator function that I instantiate then call over and over again, each time this increments the number.
def pagecnt():
n = 1
while True:
yield n
n += 1
pg = pagecnt()
print(next(pg))
print(next(pg))
print(next(pg))
This prints 1, 2 and then 3. Is there some way to combine the generator and instantiation into a new function so that I can just call
print(newfunc())
print(newfunc())
print(newfunc())
to get 1, 2 and 3?
EDIT: I don't just want to call it only 3 times. It's used to generate page number. So I don't know in advance how many times I am going to call it. And between each call, there's lots of code to calculate stuff and generate graphs.
Just create a function which instantiates your generator and calls next n times
def combine_func(n):
pg = pagecnt()
for _ in range(n):
print(next(pg))
Or we can define a wrapper function which takes in the generator instance, and returns a function which we can call to get the next element
def combine_func(pg):
def next_elem():
return next(pg)
return next_elem
pg = pagecnt()
cf = combine_func(pg)
print(cf())
print(cf())
print(cf())
You could make a simple Counter class with __call__() defined:
class Counter():
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
c = Counter()
print(c())
print(c())
print(c())
prints:
1
2
3
You could also capture the iterator in a closure and return a lambda function:
from itertools import count
def Counter():
it = count(1)
return lambda: next(it)
c = Counter()
print(c())
print(c())
print(c())
prints the same as above. In both cases it would be easy to pass in a start value if you wanted to begin somewhere other than 0.
Edit:
This is the same as using count but with your custom generator:
def Counter():
def pagecnt():
n = 1
while True:
yield n
n += 1
it = pagecnt()
return lambda: next(it)
c = Counter()
print(c())
print(c())
print(c())
Related
With a generator function, this is how itertools.count can be implemented (from documentation):
def count(start=0, step=1):
# count(10) --> 10 11 12 13 14 ...
# count(2.5, 0.5) -> 2.5 3.0 3.5 ...
n = start
while True:
yield n
n += step
I am trying to find how a similar iterator could be implemented without a generator function.
class Count:
def __init__(self, start=0, step=1):
self.c = start
self.step = step
def __iter__(self):
return self
def __next__(self):
n = self.c
self.c += self.step
return n
Is this the implementation?
For sure, it does not make any practical sense, the point is just to get an idea of how it is achieved with generators.
You could use collections.abc.Iterator to implement count with less code
from collections.abc import Iterator
class count(Iterator):
def __init__(self, start=0, step=1):
self.c, self.step = start-step, step
def __next__(self):
self.c += self.step
return self.c
One of the best practices is to write a small unit test. You can use the unittest framework for that.
Here is an example with several tests which use your class in a loop and check the values of your iterator:
import unittest
import random
class TestCount(unittest.TestCase):
loops = 20
def test_default(self):
c = Count()
for i in range(self.loops):
self.assertEqual(i, next(c))
def test_start(self):
start = random.randint(-10, 10)
c = Count(start=start)
for i in range(start, start + self.loops):
self.assertEqual(i, next(c))
def test_step_pos(self):
step = random.randint(1, 5)
c = Count(step=step)
for i in range(0, self.loops, step):
self.assertEqual(i, next(c))
def test_step_neg(self):
step = random.randint(-5, -1)
c = Count(step=step)
for i in range(0, -self.loops, step):
self.assertEqual(i, next(c))
I have the following example in which the next method of a class is supposed to return the values from two generators:
class Test():
def __next__(self):
g1, g2 = self._gen1(), self._gen2()
return next(g1), next(g2)
def _gen1(self):
i = 0
while True:
yield i
i += 2
def _gen2(self):
i = 1
while True:
yield i
i += 2
However, when I call next for this class, the values are not incremented.
>>> t = Test()
>>> next(t)
>>> (0, 1)
>>> next(t)
>>> (0, 1)
What is wrong? Is there a more eloquent way to write this class?
Although I have no idea what you are trying to accomplish, here is a cleaned up version which (I think) does what you want.
class Test():
def __init__(self):
self.g1 = self._gen2()
self.g2 = self._gen1()
def __next__(self):
return next(self.g1), next(self.g2)
def _gen1(self):
i = 0
while True:
yield i
i += 2
def _gen2(self):
i = 1
while True:
yield i
i += 2
t = Test()
print(next(t))
print(next(t))
print(next(t))
Your code doesn't work because it recreates the generator functions every time __next__() is called, which effectively resets them back to their initial state before their next next() values are returned:
def __next__(self):
g1, g2 = self._gen1(), self._gen2() # Don't do this here.
return next(g1), next(g2)
You can fix that by adding an __init__() method and initializing them in it:
class Test:
def __init__(self):
self.g1, self.g2 = self._gen1(), self._gen2() # Initialize here.
def __next__(self):
return next(self.g1), next(self.g2)
...
A more eloquent and slightly more concise way to do it which likewise will avoid the problem would be to use the builtin zip() function to create an "iterator of generators" that will return pairs of next values from each generator every time it's called. Another advantage is it's very easy to extend to handle even more generators simply just changing the __init__() method.
Here's what I mean:
class Test:
def __init__(self):
self.generators = zip(self._gen1(), self._gen2())
def __next__(self):
return next(self.generators)
def _gen1(self):
i = 0
while True:
yield i
i += 2
def _gen2(self):
i = 1
while True:
yield i
i += 2
t = Test()
for _ in range(3):
print(next(t))
Output:
(0, 1)
(2, 3)
(4, 5)
I am trying to understand how the primes iterator works, the code was taken from a lecture.
I searched for count() but found only methods for lists, objects, I just do not understand how the line self._candidates = count(1) works and what it means.
where is the object we are trying to count 1 in? and the further use of it self._candidates.next() is also very confusing.
I mainly code in java albeit know basic python.
here is the code:
class Primes(object):
def __init__(self):
self._candidates = count(1)
def __iter__(self): return self
def next(self):
item = self._candidates.next()
if item > 1:
self._candidates = FilterMultiplies(self._candidates, item)
return item
class FilterMultiplies(object):def __init__(self, seq, n):
self._seq = iter(seq)
self._n = n
def __iter__(self): return self
def next(self):
item = self._seq.next()
while item % self._n == 0:
item = self._seq.next()
return item
Probably this is itertools.count, and a line
from itertools import count
is missing from the listing.
Generators in Python are comparable to Iterators in Java. The call count(1) returns a generator that counts upwards from 1:
>>> from itertools import count
>>> counter = count(1)
>>> counter.next()
1
>>> counter.next()
2
>>> counter.next()
3
Note that counter.next() is Python 2 only. For compatibility with both Python 2 and 3, use next(counter) instead.
Basically, if I were to write a function with variable return elements, like so:
def func(elem1=True, elem2=True, elem3=True, elem4=False):
x = MyClass()
ret = []
if elem1:
ret.extend([x.func1()])
if elem2:
ret.extend([x.obj1])
if elem3:
ret.extend([x.func2().attr1])
if elem4:
ret.extend(x.list_obj3)
return ret
Things get rather long and windy. Is it possible to do something like this perhaps:
def func(elem1=True, elem2=True, elem3=True, elem4=False):
x = MyClass()
return [x.func1() if elem1,
x.obj1 if elem2,
x.func2().attr1 if elem3,
x.list_obj3 if elem4]
How neat is that!?
I know this can be done:
def func(elem1=True, elem2=True, elem3=True, elem4=False):
x = MyClass()
ret = [x.func1(), x.obj1, x.func2().attr1, x.list_obj3]
choices = [elem1, elem2, elem3, elem4]
return [r for i, r in enumerate(ret) if choices[i]]
but I would like to not calculate the elements if the user does not want them; it is a little expensive to calculate some of them.
If you hide your operations in lambdas then you can use lazy evaluation:
def func(elem1=True, elem2=True, elem3=True, elem4=False):
x = MyClass()
return [L() for inc,L in (
(elem1, lambda: x.func1()),
(elem2, lambda: x.obj1),
(elem3, lambda: x.func2().attr1),
(elem4, lambda: x.list_obj3),
) if inc]
Asking a slightly different question, can you get behaviour like matlab/octave, where you only calculate the first two results if you are assigning to two variables, without computing results 3 and 4?
For example:
a, b = func()
Python can't quite do it since func() doesn't know how many return values it wants, but you can get close using:
from itertools import islice
def func():
x = MyClass()
yield x.fun c1()
yield x.obj1
yield x.func2().attr1
yield x.list_obj3
a, b = islice(func(), 2)
I'm not sure it is better, but you could add array indexing semantics using a decorator, which would allow you to write:
#sliceable
def func():
...
a, b = func()[:2]
This is easy enough to implement:
from itertools import islice
class SlicedIterator(object):
def __init__(self, it):
self.it = it
def __iter__(self):
return self.it
def __getitem__(self, idx):
if not isinstance(idx, slice):
for _ in range(idx): next(self.it)
return next(self.it)
return list(islice(self.it, idx.start, idx.stop, idx.step))
def sliceable(f):
def wraps(*args, **kw):
return SlicedIterator(f(*args, **kw))
return wraps
Testing:
#sliceable
def f():
print("compute 1")
yield 1
print("compute 2")
yield 2
print("compute 3")
yield 3
print("compute 4")
yield 4
print("== compute all four")
a, b, c, d = f()
print("== compute first two")
a, b = f()[:2]
print("== compute one only")
a = f()[0]
print("== all as a list")
a = f()[:]
gives:
== compute all four
compute 1
compute 2
compute 3
compute 4
== compute first two
compute 1
compute 2
== compute one only
compute 1
== all as a list
compute 1
compute 2
compute 3
compute 4
I've written a program that essentially does Fibonacci by reading from a dictionary that it creates (given {0:0, 1:1} to start). Obviously if it doesn't have it in the dictionary, it will create until it does, and then later calls so it doesn't have to do the same sequence over and over again. What I'm having difficulty with is using the Counter object to keep track of the instructions done by the algorithm (so I can afterwards plot the graph of how many calls as the initial n increases). Never used class Counter before or memoization so I'm a bit lost here at the end.
dic = {0:0, 1:1}
def main():
n = int(input("Number to Fibonacci?"))
fib(n)
print(dic[n])
Counter()
memoization(n, Counter, dic)
def fib(n):
if n in dic:
return dic[n]
else:
if n < 2:
dic[n] = n
else:
dic[n] = fib(n-2) + fib(n-1)
return dic[n]
class Counter():
def __init__(self):
self._number = 0
def increment(self):
self._number += 1
def __str__(self):
return str(self._number)
def print():
print(str(self._number))
def memoization(n, Counter, dic):
if n in dic:
return dic[n]
else:
c.increment()
main()
this is what I have but I honestly don't know where to go from here, any help is greatly appreciated!
From what I can tell your Counter simply needs to be declared properly. This ensures that you pass the same instance of the Counter class along to the method.
def main():
...
my_counter_name = Counter() # <-- NAME THIS
memoization(n, my_counter_name, dic) # <-- pass the same name
And change this:
def memoization(n, Counter, dic): # <-- No need to write the class in Python
# just the local name for the variable
if n in dic:
return dic[n]
else:
c.increment()
If you would like to use c as the name for the counter as you have indicated in line 4 of the memoization method, you should change the input variable to this:
def memoization(n, c, dic):
if n in dic:
return dic[n]
else:
c.increment()
Hopefully that helps.