[Tutor] when is a generator "smart?"

eryksun eryksun at gmail.com
Sun Jun 2 17:22:35 CEST 2013


On Sat, Jun 1, 2013 at 11:58 PM, Jim Mooney <cybervigilante at gmail.com> wrote:
>
> def uneven_squares(x,y):
>     squarelist = (c**2 for c in range(x,y) if c%2 != 0)
>     return squarelist #returning a generator
>
> print(list(uneven_squares(10,10000))[2:10]) #slows as y gets bigger, then dies

I think you said you switched back to using Python 2.x. If that's
still the case, remember to use xrange() for a case like this.

2.x range() is a list factory function:

    >>> r = range(10, 10000)
    >>> itr = iter(r)
    >>> type(r), type(itr)
    (<type 'list'>, <type 'listiterator'>)

xrange is a type:

    >>> xr = xrange(10, 10000)
    >>> itxr = iter(xr)
    >>> type(xr), type(itxr)
    (<type 'xrange'>, <type 'rangeiterator'>)


Instead of using syntactic sugar like a generator expression, use a
generator function so you can see what it's doing:

    def uneven_squares(x, y):
        for c in xrange(x, y):
            if c % 2 != 0:
                yield c ** 2

    >>> g = uneven_squares(10, 10000)
    >>> type(g)
    <type 'generator'>

The compiler flags the code as a generator. Evaluation of generator
code is special-cased. It does all of the normal call setup, creates a
frame to run the code, but then instead of evaluating the frame it
returns a new generator.

The next() method (3.x __next__) evaluates the frame up to a yield:

    >>> g.next()
    121
    >>> next(g)  # use built-in next()
    169

When the frame returns instead of yielding, the generator raises
StopIteration. For example:

    def gen():
        yield 1
        return
        yield 2 # never gets here

    >>> list(gen())
    [1]

Others have already mentioned using itertools.islice(). This takes an
iterable/iterator, plus arguments for (start, stop, step) like
built-in slice(). In case you're unfamiliar with the latter, it
creates a a slice object for use in subscription. For example:

    >>> r = range(10)
    >>> s = slice(5)  # [:5]
    >>> r[s]
    [0, 1, 2, 3, 4]
    >>> s = slice(5, None)  # [5:]
    >>> r[s]
    [5, 6, 7, 8, 9]
    >>> s = slice(None, 5, 2)  # [:5:2]
    >>> r[s]
    [0, 2, 4]
    >>> s = slice(None, None, 3)  # [::3]
    >>> r[s]
    [0, 3, 6, 9]

Since many iterators don't support subscription, let alone slicing,
itertools.islice is a convenient alternative:

    >>> g = uneven_squares(10, 10000)

    >>> tuple(islice(g, 5)) # start=0,stop=5,step=1
    (121, 169, 225, 289, 361)

    >>> set(islice(g, 3))
    set([441, 625, 529])

islice returns an iterator, so it can be used efficiently to build
other sequence types, such as tuple() and set().

The second islice starts at 21**2 because the previous islice stopped
at 19**2, i.e.t he "start" and "stop" arguments are relative to the
current position in the iteration sequence.


More information about the Tutor mailing list