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 和循环(澄清)的主要内容,如果未能解决你的问题,请参考以下文章