4

I've been trying to convert this generator function into a generator expression, but with no success. Is it possible to have the same output with a generator expression?

    def gen5(num):
         for i in range(num):             
             yield i *2
             yield i *10
             yield i *i

    g = gen5(4) 
    list(g)
    [0, 0, 0, 2, 10, 1, 4, 20, 4, 6, 30, 9]

I've tried the following, but get this (None, None, None) output.

    gen_exp2 = (((yield u*2), (yield u*10) ,(yield u*u)) for u in  range(4))

    list(gen_exp2)

    [0,
     0,
     0,
     (None, None, None),
     2,
     10,
     1,
    (None, None, None),
     4,
    20,
     4,
    (None, None, None),
    6,
    30,
    9,
   (None, None, None)]

I've also done this, but it gives 4 nested tuples instead of the list above.

       gen_exp3 = (((i*2), (i*10), (i*i)) for i in range(4))

       list(gen_exp3)

       [(0, 0, 0), (2, 10, 1), (4, 20, 4), (6, 30, 9)]

Also, how can I add a parameter to a generator expression? Many thanks in advance.

Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
MichaelRSF
  • 886
  • 5
  • 16
  • 40
  • 1
    You don't need the `yield` statements in a generator expression, surrounding the loop with parentheses is enough. – Delgan Nov 19 '17 at 17:32
  • That doesn't generate the same output like the generator function. It gives 4 tuples. – MichaelRSF Nov 19 '17 at 17:34
  • You will need another nested for loop to "unpack" your tuples. – Delgan Nov 19 '17 at 17:35
  • `(x for i in range(num) for x in (i*2, i*10, i*i))` works, but is confusing to read. `itertools.chain.from_iterable((i*2, i*10, i*i) for i in range(num))` too – Patrick Haugh Nov 19 '17 at 17:36
  • Awesome. Thanks Patrick. I thought for a moment that I couldn't convert a generator function into a gen expression. – MichaelRSF Nov 19 '17 at 17:38

2 Answers2

3

You just need a double loop in the gen exp:

num = 4
g = (j for i in range(num) for j in (i*2, i*10, i*i))
print(*g)

output

0 0 0 2 10 1 4 20 4 6 30 9

As Moses says, using yield in a gen exp is not a good idea.


Here's a cute variation, using map

g = (j for i in range(num) for j in map(i.__mul__, (2, 10, i)))

However, some people might not like that use of i.__mul__.


You asked: "How can I add a parameter to a generator expression?". Well, you can't. You could create a lambda, as Moses shows, but really you're better off making a proper generator function. Lambdas are supposed to be anonymous functions, so creating a lambda and binding it to a name is a rather dubious practice. ;)

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • @JonClements I suppose one could. It'd be interesting to know how it compares speed-wise to `i.__mul__`. – PM 2Ring Nov 19 '17 at 18:00
  • I'd be surprised if it wasn't slower because of additional function calls and lookups... – Jon Clements Nov 19 '17 at 18:01
  • @JonClements Me too. OTOH, I guess `partial` could be using some tricky C magic to do its stuff. The CPython source for functools has a Python wrapper [here](https://github.com/python/cpython/blob/master/Lib/functools.py#L230). I'm not having any luck trying to find the C source. Ah, [here we go](https://github.com/python/cpython/blob/master/Modules/_functoolsmodule.c) – PM 2Ring Nov 19 '17 at 18:10
  • My assumption would be that partial is somewhat slower because the `mul` indirection, and because it does need to make a 2-tuple for the arguments. I didn't try it yet though – Antti Haapala -- Слава Україні Nov 19 '17 at 21:15
2

You don't need to use yield in a generator expression.

See yield in list comprehensions and generator expressions on why this could easily be considered a bug, although it isn't.

You can use a nested comprehension to achieve the same thing in a generator expression:

gen_exp2 = (x for u in range(4) for x in (u*2, u*10, u*u))

And you can parameterise this by putting the generator expression in a function:

gen_fun = lambda num: (x for u in range(num) for x in (u*2, u*10, u*u)) 

Might be better to keep your original approach though, as using yield in a function may be faster than using a nested comprehension within a generator expression which is already pretty slow without nesting.

Delgan
  • 18,571
  • 11
  • 90
  • 141
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • Understood your answer. Can you please provide the explanation for that output shown in the question? Just curious to know :) – Abhijith Asokan Nov 19 '17 at 17:41
  • @stack_n_queue Did you read the linked answer? Pretty much explains how the interpreter treats a `yield` in a gen. exp. and what happens behind the scenes: stack etc. – Moses Koledoye Nov 19 '17 at 17:45