python: lambda、yield-statement/expression 和循环(澄清)

Posted

技术标签:

【中文标题】python: lambda、yield-statement/expression 和循环(澄清)【英文标题】:python: lambda, yield-statement/expression and loops (Clarify) 【发布时间】:2017-03-28 10:40:53 【问题描述】:

TL;DR:我们可以在lambda 中实现yield 或生成器语句(带有循环)吗?

我的问题是澄清:

是否可以用yield实现下面的简单循环功能

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

导致错误:

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

看起来,它期望某些东西作为未写入返回语句的正确操作数,但发现 yield 并感到困惑。

是否有适当的合法方式来循环实现这一目标?

旁注:yield 可以是语句/表达式,具体取决于您询问的对象:yield - statement or expression?

最终答案: yield 可以与 lambda 一起使用,但限制(单行)使其无用。 for/while 在 lambda 中不可能,因为它们不是表达式。 -user2357112 隐式 for 循环可以使用列表推导,并且 yield 在列表推导中有效。 -wim

Verdict- 无法使用显式循环,因为 python 中的 lambda 只能包含表达式,要编写显式循环,您需要使用语句。 -wim

【问题讨论】:

“但你也可以使用像print() 这样的语句,只要它包含在一行中” - 错误! print 是 Python 3 中的函数,print 调用是普通表达式。您不能在 yield 调用中使用任意单行语句。 试图用lambda 写这个是没有意义的。如果您想将其填充到一行,(x for x in range(0, 15)) 将是您的生成器函数的直接 genexp 翻译。 我同意。我试图检验一个理论。再想一想,由于lambda 必须放入单个语句的限制,我怀疑我能否实现任何在lambda 中具有循环的东西。但最终澄清这一点会很好,因为它一直困扰着我。 虽然从技术上讲,您可以将yield 放在 lambda 函数中,但 lambda 函数的约束使其基本上没有用处。 你必须用括号括起来(yield x)。但是整个 for 循环语法无论如何都不是有效的表达式。 【参考方案1】:

您似乎尝试创建的单行在技术上实际上可以使用 lambda,您只需要更多地帮助解析器:

>>> 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

这在列表推导中隐式使用了 for 循环。在理解之外使用显式的while 循环或for 循环是不可能的。这是因为 python 中的 lambda 只能包含 expressions,而要编写显式循环,您需要使用 statements。

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

【讨论】:

是的。就像有人指出的那样,它基本上是一个生成器 obj。你知道是否可以使用for/while 完成。此外,你真的不能用 lambda 单行做任何事情。 请注意,Python 2 上的结果完全不同。这段代码以一种非常微妙的方式依赖于这样一个事实,即在 Python 3 上,列表推导是用 another 匿名实现的由lambda创建的匿名函数内的函数。 哇,这是怎么回事?你能解释一下[(yield x) for x in range(15)] 的作用吗?表达式返回什么? 它返回一个生成器对象。当您迭代生成器时,您将获得在理解迭代期间产生的值。迭代完成后,StopIteration 异常实例将具有value 属性。这将是列表理解结果:它将是一个长度为 15 的列表,并且该列表的元素将是 发送 到生成器中的项目。 你也可以这样做——懒惰地评估每个函数,并且不会在 3.8 中被弃用:lamyield = lambda: ((yield expensive_func1()), (yield expensive_func2()), (yield expensive_func3()))【参考方案2】:

如果你可以用生成器重写它,那么在lambda 内部使用yield 是否有必要?

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

【讨论】:

【参考方案3】:

您实际上可以通过有用的方式循环 lambda,只是您提供的示例不是一个很好的用例。

您可能希望在lambda 中使用yield 的一个实例可能是仅在需要时才延迟执行昂贵的函数。像这样:

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!")

输出:

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

请注意,昂贵的检查只发生在每个循环的开始,而不是一次全部发生。另请注意,此语法在 Python 3.8+ 中仍然有效,因为它没有在推导式中使用 yield

【讨论】:

【参考方案4】:

我有一个更简单的解决方案

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]

这将创建一个返回生成器的函数。这是多次访问生成器的更简单方法。

换个版本

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

这是性能差异

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)

【讨论】:

以上是关于python: lambda、yield-statement/expression 和循环(澄清)的主要内容,如果未能解决你的问题,请参考以下文章

Python--lambda函数

Python 中的 lambda 和lambda 有啥区别

Python中的匿名函数——lambda函数

Python | Lambda 函数

Python lambda介绍

Python lambda介绍