Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to convert generator into iterator class

Consider the following dummy example:

def common_divisors_generator(n, m):

    # Init code
    factors_n = [i for i in range(1, n + 1) if n%i == 0]
    factors_m = [i for i in range(1, m + 1) if m%i == 0]

    # Iterative code
    for fn in factors_n:
        for fm in factors_m:
            if fn == fm:
                yield fn

# The next line is fast because no code is executed yet
cdg = common_divisors_generator(1537745, 373625435)
# Next line is slow because init code is executed on first iteration call
for g in cdg:
    print(g)

The init code, which takes a long time to compute, is executed once the generator has been iterated for the first time (as opposed to when the generator it is initialized). I would prefer that the init code it is executed as the generator is initialized.

For this purpose I convert the generator into an iterator class as follows:

class CommonDivisorsIterator(object):

    def __init__(self, n, m):
        # Init code
        self.factors_n = [i for i in range(1, n + 1) if n%i == 0]
        self.factors_m = [i for i in range(1, m + 1) if m%i == 0]

    def __iter__(self):
        return self

    def __next__(self):
        # Some Pythonic implementation of the iterative code above
        # ...
        return next_common_divisor

All ways I can think of implementing the __next__ method above are very cumbersome as compared to the simplicity of the iterative code in the generator with the yield keyword.

What would be the most Pythonic way of implementing the __next__ method in the iterator class?

Alternatively, how can I modify the the generator so that the init code is executed at init time?

like image 208
Daniel Arteaga Avatar asked Jul 18 '18 08:07

Daniel Arteaga


People also ask

Which is faster generator or iterator?

To write a python generator, you can either use a Python function or a comprehension. But for an iterator, you must use the iter() and next() functions. Generator in python let us write fast and compact code. This is an advantage over Python iterators.

How iterator can be used to generate the generator?

Yes, We can create a generator by using iterators in python Creating iterators is easy, we can create a generator by using the keyword yield statement. Python generators are an easy and simple way of creating iterators. and is mainly used to declare a function that behaves like an iterator.

Does a generator return an iterator?

Generator functions are written using the function* syntax. When called, generator functions do not initially execute their code. Instead, they return a special type of iterator, called a Generator.

Is generator an iterator or iterable?

A generator is technically an iterator, basically, it's a way to define iterator protocol in a compact way. A classic iterator will be defined using a class with __iter__ and __next__ methods, with a generator you can do this with just a function with yield statements or generator expressions.


1 Answers

In both cases (whether you use a function or a class), the solution is to split the implementation into two functions: a setup function and a generator function.

Using yield in a function turns it into a generator function, which means that it returns a generator when it's called. But even without using yield, nothing's preventing you from creating a generator and returning it, like so:

def common_divisors_generator(n, m):
    factors_n = [i for i in range(1, n + 1) if n%i == 0]
    factors_m = [i for i in range(1, m + 1) if m%i == 0]

    def gen():
        for fn in factors_n:
            for fm in factors_m:
                if fn == fm:
                    yield fn

    return gen()

And if you're using a class, there's no need to implement a __next__ method. You can just use yield in the __iter__ method:

class CommonDivisorsIterator(object):
    def __init__(self, n, m):
        self.factors_n = [i for i in range(1, n + 1) if n%i == 0]
        self.factors_m = [i for i in range(1, m + 1) if m%i == 0]

    def __iter__(self):
        for fn in self.factors_n:
            for fm in self.factors_m:
                if fn == fm:
                    yield fn
like image 119
Aran-Fey Avatar answered Sep 29 '22 00:09

Aran-Fey