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.)
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)?
The yield keyword instructs Ruby to execute the code in the block. In this example, the block returns the string "yes!" . An explicit return statement was used in the cool() method, but this could have been implicit as well.
What about Ruby? Well, the Ruby language does not have generators. It does have Fibers, which can accomplish pretty much the same thing1, but, more interestingly from a learning perspective, it has native support for continuations.
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
))
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())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With