【问题标题】:python: lambda, yield-statement/expression and loops (Clarify)python: lambda、yield-statement/expression 和循环(澄清)
【发布时间】: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 循环语法无论如何都不是有效的表达式。

标签: python python-3.x loops lambda generator


【解决方案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)
      
      

      【讨论】:

        猜你喜欢
        • 2017-11-26
        • 2012-09-21
        • 2014-06-26
        • 1970-01-01
        • 1970-01-01
        • 2015-08-28
        • 2016-01-02
        • 2014-05-11
        相关资源
        最近更新 更多