Python中相当于Ruby的yield的是什么?

16

我正在从 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专家嘲笑)?


1
Python 没有这种构造。 yield 主要用于实现迭代器。你可以有一些方法让它们做你想要的事情,但函数装饰器或上下文管理器是更好的方法。(实际上,contextlib 为您执行此类 "黑科技"。) - millimoose
你能解释一下在 Ruby 中 yield 语句的作用吗?我尝试回答了一下,但由于我不懂 Ruby,所以不知道是否正确。 - mgilson
1
yield在Ruby中 放弃了对传入的&block的控制 - 本质上,它要求一种传递和运行任意代码块的方式。 - Hannele
顺便说一下,谢谢你问这个问题。我一直想学习ruby已经有一段时间了。我想这是我离掌握如何使用它更近了一步。 - mgilson
3个回答

13

进一步了解 Ruby 中的 yield,看起来你需要类似于 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))


@millimoose -- 你似乎知道 ruby 在做什么,我毫不怀疑你知道如何使用 contextlib。为什么不写一个答案,这样就可以学到一些东西了?:-P - mgilson
唉,现在只能用手机了 :P 无论如何,鉴于OP的例子中with_quietude()块没有真正使用本地变量,所以这个答案基本上就是我会想出来的。 - millimoose
我认为Python的等价物应该将quietude()作为类的方法,并读取并赋值给self.gval。这样就不需要全局变量了。 - user4815162342
@fearless_fool 关于全局变量的评论更多是想说我知道Ruby的块比Python中等效使用函数对象更强大。然而,在上下文管理器的情况下,你并没有真正这样做。Python对协程有一些支持,这使得with语句具有与封闭函数相同的变量作用域。(即使最终实现方式谁知道。) - millimoose
@fearless_fool:或者,另一种方式是:Python 无法做到但 Ruby 可以做到的事情,但我记不起来是什么了,而且这也不是你想要做的事情。 - millimoose
显示剩余3条评论

4

如果你是从Ruby转过来的,需要注意一点:所有的Python 'def'基本上都等同于ruby的'proc'。

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的那样灵活。

要传递带有参数的函数,我建议使用partial函数,请参见下面的代码:

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不同,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(): ...)。但是,如果您想传递参数(但推迟评估直到进入 quietude()),您可以将其包装在 lambda 中,例如 quietude(3, lambda: my_func('coltrane'))。这是您的做法吗? - fearless_fool
Python函数在添加“()”时被调用。如果你传入“func()”,它将执行名为func的函数并传递结果。如果你传入“func”,那么它不会执行func,直到你使用“()”调用它。 - andy boot

1

我喜欢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世界的人的预期。

不错的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接