I’ve recently started coding in Python more than I’ve used to, and it’s interesting for me to have a closer look to its features and compare them with similar Julia’s ones.
The do
-block in the Julia programming language and the with
statement in
Python can appear to be similar because of some common uses (most notably,
opening a stream and automatically closing it at the end), but are fundamentally
different. In this post I will review them.
with
statement in Python
The with
statement has
been introduced in Python with PEP
343 to simplify the control flow of
try
/finally
statements.
A good explanation of this feature is given at
http://effbot.org/zone/python-with-statement.htm. In general, you can use
try
/finally
to handle unmanaged resources like streams:
def controlled_execution(callback):
set things up
try:
callback(thing)
finally:
tear things down
def my_function(thing):
do something with thing
controlled_execution(my_function)
For an object of a class, it is possible to reorganize the above control flow by
using the with
statement:
class controlled_execution:
def __enter__(self):
set things up
return thing
def __exit__(self, type, value, traceback):
tear things down
with controlled_execution() as thing:
do something with thing
The __enter__
method prepares the object for later use and returns thing
,
which can be then consumed in the body of with
. The __exit__
method will be
called in any case at the end of the body of with
and can be used to close the
previously opened resources and handle any exception raised within the with
body, if necessary.
Opening a file with with
A common application of the with
statement is the open
function:
with open(filename, mode='w') as f:
do something with f
Just to practically see how the __enter__
and __exit__
methods can be
defined, we implement a very simple class, MyFile
, to replicate the behaviour
of the builtin open
function when opening a file:
class MyFile:
def __init__(self, file):
self.file = file
def __enter__(self):
return self.file
def __exit__(self, type, value, traceback):
return self.file.close()
def read(self):
return self.file.read()
def MyOpen(name, mode='r'):
return MyFile(open(name, mode))
with MyOpen('file.txt') as f:
f.read()
do
-block in Julia
The
do
-block
in Julia addresses a different problem: write in a different way the passing of
a function as argument to another function.
In a simple way, any function func
accepting a function as first argument:
function func(f::Function, x, y, z...)
# do something with arguments x, y, z..., and call f at some point
end
can be called as
func(x, y, z...) do args
# put here the body of function f(args) that will
# be passed as first argument to func(f, x, y, z...)
end
For example, the
filter
enables filtering a collection by removing those elements for which the provided
function returns false
. We can select the odd numbers between 1 and 100
divisible by 3 with
julia> filter(x -> isinteger(x/3) && isodd(x), 1:100)
17-element Array{Int64,1}:
3
9
15
21
27
33
39
45
51
57
63
69
75
81
87
93
99
Here we passed an anonymous
function
as first argument to filter
in order to select the desired elements of the
collection passed as second argument. We can rewrite this by using the
do
-block in the following way:
julia> filter(1:100) do x
isinteger(x/3) && isodd(x)
end
17-element Array{Int64,1}:
3
9
15
21
27
33
39
45
51
57
63
69
75
81
87
93
99
The code in the body of the do
-block is used to create an anonymous function,
whose arguments are specified after do
, that is passed as first argument to
the function before the do
.
Opening a file with do
A common use of the do
-block is to open a file and automatically close it when
you’re done, just like the with
statement in Python. For example:
open("outfile", "w") do io
write(io, data)
end
In Julia’s Base code, this is achieved by extending the open
method in the
following way:
function open(f::Function, args...; kwargs...)
io = open(args...; kwargs...)
try
f(io)
finally
close(io)
end
end
This method, which accepts a function as first argument, is what makes it
possible to use the do
-block in the example above: after effectively opening
the file, the body of the do
-block is executed in the try
statement,
and in the end the finally
clause
takes care of safely closing the file, even in case of errors while executing
the function f
.
Summary
The with
statement in Python is a syntactic sugar for try
/finally
. This
construct is bound to classes, this means that the __enter__
/__exit__
methods are bound to a specific class and they can be defined only once as they
have a fixed signature, so you can’t have multiple __enter__
/__exit__
handlers within the same class, or independently from a class. In order for the
with
statement to work, both __enter__
and __exit__
must be defined in any
case, however if you don’t really need the try
/finally
construct you can
still take advantage of the handy with
syntax, for example by making the
__exit__
method dummy.
The do
-block in Julia is not bound to any type, the only condition to use it
is to have a function taking a function as first argument. The function may
contain a control flow construct, like try
/finally
, but this is not
mandatory. You can also have different methods for a function to be used with
the do
-block, depending on the types and/or the number of the other arguments.