python: lambda, yield-statement/expression and loops (Clarify)

Question:

Can we implement yield or generator statement (with a loop) within a lambda?

My question is to clarify:

Whether the following simple loop function can be implemented with yield

def loopyield():
   for x in range(0,15):
      yield x
print(*loopyield())

Results in error:

lamyield=lambda x: yield x for x in range(0,15)
                       ^
SyntaxError: invalid syntax

Which looks like, it was expecting something as right operand for unwritten return statement but found the yield and getting confused.

Is there a proper legit way to achieve this in a loop?

Side note: yield can be statement/expression depending on who you ask: yield – statement or expression?

Final Answer : yield can be used with lambda but the limitation(single-line) makes it useless. for/while not possible in lambda because they are not expressions. -user2357112 implicit for loop is possible with list comprehension, and yield is valid within the list comprehension. –wim

Verdict- Explicit loops not possible because lambdas in python can only contain expressions, and to write an explicit loop you will need to use statements. -wim

Asked By: theMobDog

||

Answers:

The one-liner you seem to be trying to create is actually technically possible with a lambda, you just need to help the parser a bit more:

>>> lamyield = lambda: [(yield x) for x in range(15)]
>>> print(*lamyield())
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

This uses a for loop implicitly in a list comprehension. It is not possible with an explicit while loop or for loop outside of a comprehension. That’s because lambdas in Python can only contain expressions, and to write an explicit loop you will need to use statements.

Note: this syntax is deprecated in Python 3.7, and will raise SyntaxError in Python 3.8

Answered By: wim

Is it any necessity for using yield inside of lambda if you can rewrite it with generator such that?

In[1]: x = (i for i in range(15))
In[2]: x
Out[2]: <generator object <genexpr> at 0x7fbdc69c3f10>

In[3]: print(*x)
Out[3]: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

In[4]: x = (i for i in range(0, 15))
In[5]: x.__next__()
Out[5]: 0

In[6]: next(x)
Out[6]: 1
Answered By: ncopiy

You actually can loop through a lambda in useful ways, it’s just that the example you provided isn’t a great use case.

One instance where you might want to use yield inside a lambda might be to lazily execute expensive functions only when needed. Like so:

def expensive_check1():
    print("expensive_check1")
    return True


def expensive_check2():
    print("expensive_check2")
    return True


def expensive_check3():
    print("expensive_check3")
    return True


def do_the_thing(*args):
    print(args)


if __name__=="__main__":
    for check, args in (lambda: (
                                (yield (expensive_check1(), ["foo", "bar"])), 
                                (yield (expensive_check2(), ["baz"])),
                                (yield (expensive_check3(), [])),
                        ))():
        if check:
            do_the_thing(*args)
            continue
        raise Exception("oh noes!!!")
    else:
        print("all OK!")

Output:

expensive_check1
('foo', 'bar')
expensive_check2
('baz',)
expensive_check3
()
all OK!

Note that the expensive checks only happen at the start of each loop, rather than all at once. Also note that this syntax will still work in Python 3.8+, since it is not using the yield inside of a comprehension.

Answered By: Rick

I have a simpler solution

lmbdgen = lambda: (x for x in range(15))
lmbdgen
Out[40]: <function __main__.<lambda>()>
lmbdgen()
Out[41]: <generator object <lambda>.<locals>.<genexpr> at 0x00000171473D8D60>
list(lmbdgen())
Out[42]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

This creates a function that returns a generator. This is a simpler way to access a generator multiple times.

For an alternative version

def defgen(): yield from (x for x in range(5))
def defgenyield(): return (x for x in range(5))

Here is the performance difference

def defgen_return(): return (x for x in range(10000))
def defgen_yield(): yield from (x for x in range(10000))
lmbdgen = lambda: (x for x in range(10000))

%timeit list(defgen_return())
384 µs ± 4.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit list(defgen_yield())
563 µs ± 9.43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit list(lmbdgen())
387 µs ± 5.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)