【问题标题】:what is the Python equivalent of Ruby's yield?Ruby 的产量的 Python 等价物是什么?
【发布时间】:2013-04-30 13:55:18
【问题描述】:

我正在为一个项目从 Ruby 切换到 Python。我很欣赏 Python 具有一流的函数和闭包这一事实,所以这个问题应该很容易。我只是还没弄清楚 Python 的习惯用法是什么:

在 Ruby 中,我可以这样写:

def with_quietude(level, &block)
  begin
    saved_gval = gval
    gval = level
    yield
  ensure
    gval = saved_gval
  end
end

然后这样称呼它:

with_quietude(3) {
  razz_the_jazz
  begin_the_beguine
}

(注意:我不是在问 Python try/finally 的处理,也不是关于保存和恢复变量的问题——我只是想要一个在其他代码中包装块的重要示例。)

更新

或者,当我真正询问闭包时,由于上一个示例中的一些答案被挂断在全局分配上,如果调用如下所示怎么办? (注意这不会改变 with_quietude 的定义):

def frumble(x)
  with_quietude {
    razz_the_jazz(x)
    begin_the_beguine(2 * x)
  }
end

您将如何在 Python 中实现类似的东西(并且不会被 Python 专家嘲笑)?

【问题讨论】:

  • Python 并没有这个结构。 yield 主要用于实现迭代器。你可以让他们做你想做的事,但函数装饰器或上下文管理器是更好的方法。 (事实上​​,contextlib 为你做了这种“破解”。)
  • 您能解释一下yield 语句在ruby 中的作用吗?我试了一下答案,但我不知道它是否正确,因为我不知道红宝石......
  • yield in Ruby gives up control to the &block which was passed in - 本质上,他是在寻求一种传入和运行任意代码块的方法。
  • 顺便说一句,感谢您提出这个问题。一段时间以来,我一直想学习ruby。我想这是一件事,我会更接近知道如何使用。

标签: python ruby closures


【解决方案1】:

进一步研究 ruby​​ 的产量,您似乎想要contextlib.contextmanager 之类的东西:

from contextlib import contextmanager

def razz_the_jazz():
    print gval

@contextmanager
def quietude(level):
    global gval
    saved_gval = gval
    gval = level

    try:
        yield
    finally:
        gval = saved_gval

gval = 1

with quietude(3):
     razz_the_jazz()

razz_the_jazz()

此脚本输出:

3
1

表明我们的上下文管理器确实在全局命名空间中重置了gval。当然,我不会使用这个上下文管理器,因为它只在全局命名空间中工作。 (例如,它不适用于函数中的本地人)。

这基本上是对赋值如何创建对对象的新引用的限制,并且您永远不能通过直接赋值来改变对象。 (改变对象的唯一方法是分配给它的一个属性或通过__setitem__ (a[x] = whatever))

【讨论】:

  • 但是,您应该能够以某种方式使用上下文对象的属性在作用域之间进行通信。 (尽管在 OP 的示例中似乎不需要这样做
  • @millimoose -- 你似乎知道ruby 在做什么,我毫不怀疑你知道如何使用contextlib。为什么不写一个答案让可以学到一些东西呢? :-P
  • 唉,现在卡在手机上:P 无论如何,鉴于 OP 的示例,该块在 with_quietude() 中与当地人并没有真正做任何事情,这个答案基本上就是我想要的跟上。
  • 我认为 Python 等效项会将 quietude() 作为类上的方法,并会读取并分配给 self.gval。这样就不需要全局变量了。
  • @mgilson(和 millimoose):我更新了我的示例以表明 yield 块对全局变量不感兴趣,而是一个完整的闭包,可以访问它所在的局部变量创建的。这会改变答案吗?
【解决方案2】:

如果您来自 Ruby,请注意:所有 python 'def's 基本上与 ruby​​ 'proc's 相同。

Python 没有对应于 ruby​​ 的“def”

通过在调用函数的范围内定义自己的函数,您可以获得与您所要求的非常相似的行为

def quietude(level, my_func):
    saved_gval = gval
    gval = level
    my_func()

def my_func():
  razz_the_jazz()
  begin_the_beguine()

quietude(3, my_func)

---- 编辑:要求提供更多信息:-----

Python 的 lambda 被限制为一行,因此它们不像 ruby​​ 那样灵活。

要传递带参数的函数,我建议部分函数参见以下代码:

import functools

def run(a, b):
    print a
    print b

def runner(value, func):
    func(value)

def start():
    s = functools.partial(run, 'first')
    runner('second', s)

---- 编辑 2 更多信息----

Python 函数仅在添加 '()' 时才会被调用。这与 '()' 是可选的 ruby​​ 不同。下面的代码在 start() 中运行 'b_method',在 run() 中运行 'a_method'

def a_method():
    print 'a_method is running'
    return 'a'

def b_method():
    print 'b_method is running'
    return 'b'

def run(a, b):
    print a()
    print b

def start():
    run(a_method, b_method())

【讨论】:

  • 这感觉更接近了。与 Ruby 的 proc 不同,Python 的 def 不返回值,所以你不能做 quietude(3, def my_func(): ...)。但是,如果您想传递 args(但将评估推迟到您进入 quietude() 之前,您可以将其包装在 lambda 中,例如 quietude(3, lambda: my_func('coltrane'))。您会这样做吗?
  • 添加 '()' 时会调用 python 函数。如果你传入'func()',它将执行名为func的函数并将结果传入。如果你传入'func',它不会执行func,直到你用'()'调用它
【解决方案3】:

我喜欢 mgilson 给出的答案,所以它得到了检查。对于来自 Ruby 世界的人来说,这只是对 @contextmanager 功能的一个小扩展。

gval = 0

from contextlib import contextmanager

@contextmanager
def quietude(level):
    global gval
    saved_gval = gval
    gval = level
    try:
        yield
    finally:
        gval = saved_gval

def bebop(x):
  with quietude(3):
    print "first", x*2, "(gval =", gval, ")"
    print "second", x*4, "(gval =", gval, ")"

bebop(100)
bebop("xxxx")

打印出来:

first 200 (gval = 3 )
second 400 (gval = 3 )
first xxxxxxxx (gval = 3 )
second xxxxxxxxxxxxxxxx (gval = 3 )

这表明with 范围内的所有内容都可以访问词法封闭变量,并且其行为或多或少与来自 Ruby 世界的人所期望的方式相同。

好东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-14
    • 2011-04-12
    • 1970-01-01
    • 2014-12-29
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多