Python Context Manager Compared to Ruby DSLs

Python has the with keyword that is analogous to using in C#. It is used to manage resource access and wrap a block of code with setup and cleanup stuff. I like it.

Here’s an example adapted from the Python Enhancement Proposal (PEP) 343 that introduced the implementation in Python 2.6:

from contextlib import contextmanager

@contextmanager
def opening(filename):
   f = open(filename)
   try:
       yield f
   finally:
       f.close()

with opening("the/filename") as f:
    for line in f:
        print line

I like it so much because it reminds me of the block-based resource access I’m now used to thanks to Ruby and Swift spoiling me for years. I’m not a fan of repeating imperative sequences between opening/closing file handle code since I know you can do it this way.

I’m still learning Python. That’s why I sometimes try to come up with equivalent Ruby code (which is my reference language of choice to explain new concepts in familiar terms). I noticed that the Python example above sports the yield keywords, which is how blocks are called in Ruby, too. So I wondered what the @contextmanager decorator is useful for, and why the with keyword relies on the result of the expression to respond to the context manager protocol. This protocol is defined through the __enter__ and __exit__ methods being available. That’s how the block is wrapped.

In Ruby, there’s no language feature like decorators. These are basically function call wrappers, but you define the wrapping when you define the function, instead of at the call site. The code above is similar to contextmanager(open(filename)); that’s the gist of decorators as far as I know.

How do you implement the stuff above in Ruby then?

class MyFile
  def self.opening(filename)
    f = File.open(filename)
    yield f
  ensure
    f.close
  end
end

MyFile.opening("the/filename") do |f|
  f.each_line do |line|
    puts line
  end
end

That’s it, as far as I can tell. The (implicit) block passed to MyFile#opening is invoked with the yield keyword. There’s no built-in functionality apart from this. It’s similar to the Python code – but only on the surface level. Python has interesting things going on behind the scenes in the implementation of the with keyword, and then there’s the code for the @contextmanager decorator itself.

I think the crucial language difference here is that Ruby is designed to make writing block-based methods so easy. By design, Ruby encourages coming up with domain-specific languages (DSLs).

Python’s with keyword reads nicer, but then again you can write this as part of your DSL in Ruby, too, if you support optional blocks in the wrapped method. It does not make sense, but there you go:

class MyFile
  def self.opening(filename)
   
  end

  def self.opening(filename, &block)
    # Wrap the method body in a lamda so we can either run or return it
    impl = ->(&inner_block) { 
      f = File.open(filename)
      try
        # Explicit version of `yield`
        inner_block.call(f)
      ensure
        f.close
      end
    }
    
    # Return the lambda for later execution if this is 
    # not a direct call with a block
    return impl if block.nil?
    
    # If we pass a block, execute right away!
    impl.call(&block)
  end
end

def with(wrapped, &block)
  wrapped.call(&block)
end


MyFile.opening("the/filename") do |f|
  puts "regular:"
  puts f.readlines
end

with MyFile.opening("wrapped/fn") do |f|
  puts "wrapped:"
  puts f.readlines
end