fearless_fool
fearless_fool

Reputation: 35229

what is the Python equivalent of Ruby's yield?

I'm switching from Ruby to Python for a project. I appreciate the fact that Python has first-class functions and closures, so this question ought to be easy. I just haven't figured out what is idiomatically correct for Python:

In Ruby, I could write:

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

and call it like this:

with_quietude(3) {
  razz_the_jazz
  begin_the_beguine
}

(Note: I'm not asking about Python try/finally handling nor about saving and restoring variables -- I just wanted a non-trivial example of wrapping a block inside some other code.)

update

Or, since some of the answers are getting hung up on the global assignments in the previous example when I'm really asking about closures, what if the call was as follows? (Note that this doesn't change the definition of with_quietude):

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

How would you implement something similar in Python (and not get laughed at by the Python experts)?

Upvotes: 16

Views: 3555

Answers (3)

fearless_fool
fearless_fool

Reputation: 35229

I like the answer that mgilson gives, so it gets the check. This is just a small expansion on the capabilities of @contextmanager for someone coming from the Ruby world.

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

This prints out:

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

This shows that everything within the scope of the with has access to the lexically closed variables, and behaves more or less the way someone coming from the Ruby world would expect.

Good stuff.

Upvotes: 1

andy boot
andy boot

Reputation: 11757

A word of warning if you are coming from Ruby: All python 'def's are basically the same as ruby 'proc's.

Python doesn't have an equivalent for ruby's 'def'

You can get very similar behaviour to what you are asking for by defining your own functions in the scope of the calling function

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)

---- EDIT: Request for further information: -----

Python's lambdas are limited to one line so they are not as flexible as ruby's.

To pass functions with arguments around I would recommend partial functions see the below code:

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)

---- Edit 2 More information ----

Python functions are only called when the '()' is added to them. This is different from ruby where the '()' are optional. The below code runs 'b_method' in start() and 'a_method' in run()

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

Upvotes: 4

mgilson
mgilson

Reputation: 310097

Looking more into ruby's yield, it looks like you want something like 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()

This script outputs:

3
1

indicating that our context manager did reset gval in the global namespace. Of course, I wouldn't use this context manager since it only works in the global namespace. (It won't work with locals in a function) for example.

This is basically a limitation of how assignment creates a new reference to an object and that you can never mutate an object by assignment to it directly. (The only way to mutate an object is to assign to one of it's attributes or via __setitem__ (a[x] = whatever))

Upvotes: 13

Related Questions