Ruby-styled blocks in Python have been something to be a bit anxious
about. After spending even a little time working with Ruby, it’s really
hard to get the idioms that blocks facilitate out of your head. Dealing
with certain types of problems with coroutines just feels right:
File.open("bla.txt") do |file|
file.read()
end
With the equivalent Python being roughly:
file = open('foo.txt')
try:
file.read()
finally:
file.close()
Eewww. What attracted me to Python was its ability to make code read
very closely to its intent as it would be described by a human. Resource
aquisition/release is one of those things that I’d rather not have to
read about when I’m trying to extract the essence of what a piece of
code is doing.
Of course, you can get close in most cases by using normal callable
passing…
def with_open_file(file, block):
try:
block(file)
finally:
file.close()
def do_stuff(file):
file.read()
with_open_file(open('bla.txt'), do_stuff)
… but the Ruby block syntax reads better to these eyes and it seems
I’m not the only one. A few weeks ago, Guido had this to say
about the block style in relation to recent PEP activity:
That was all before I (re-)discovered yield-expressions (in Ruby!),
and mostly in response to the most recent version of PEP 288, with its
problem of accessing the generator instance. I now strongly feel that
g.next(EXPR) and yield-expressions are the way to go.
So I’ve been following the succession of PEPs that have led us to
PEP-342, Coroutines via Enhanced Generators, and
PEP-343, Anonymous Block Redux and Generator
Enahncements, with great interest.
It looks like there’s a decent chance that we’ll be able to stuff like
this in Python 2.5:
@with_template
def opening(filename, mode="r"):
file = open(filename, mode)
try:
yield file
finally:
file.close()
with opening("/etc/passwd") as file:
file.read()
This is accomplished not by adding an implicitly passed block construct
like Ruby’s &block
, but by adding a basic message passing protocol for
generators. Generators will have two new methods: send
and
throw
. The important one here is throw
, which tells the generator to
raise an Exception
at its suspension point. All of this is hidden
behind the implementation of with_template
(sample implementation in
PEP-342).
What I’m interested in understanding more fully is how the PEP proposed
enhancements to generators will work in iterative cases or whether
that’s planned at all. The examples seem targeted toward resource
aquisition/release (which is fine, there’s definitely a strong set of
use cases in that area). But I’m interested in understanding how cases
similar to Ruby’s IO.foreach
will be handled:
IO.foreach("testfile") { |line| puts line }
Note that this is different from Python’s…
for line in open('testfile').readlines():
print line
… for a few reasons. First, resource aquisition/release is handled
within IO.foreach
where in the preceding Python snippet, it isn’t
really handled at all. Second, IO.foreach
reads the file iteratively,
calling the block each time, where as an approach using Python
generators, such as follows…
def lines(filename):
f = open(filename)
try:
line = f.readline()
while line:
yield line
line = f.readline()
finally:
f.close()
for line in lines('foo.txt'):
raise Exception("this doesn't work")
… falls apart because the exception raised in the iterating block is
not automatically signaled back to the generator. There’s no guarantee
that the finally block will execute when the iterating block exits.
I have a feeling there’s some aspect of this that I’m not fully
grasping. Perhaps Phillip (PING) or someone else with a good understanding of
the proposed generator enhancements can stop by and comment; inquiring
minds want to know…