Context Managers

the with statement

by Kurt Neufeld

What is a context manager?

An excellent question!

But first, why C sucks

Why C sucks

There are many reasons why C sucks but they mostly all boil down to the fact that C doesn't have a destructor.

C++ has destructors and as a result C++ is way better.

Destructors

If you're not familiar with destructors, they're a function that is automatically run when a variable goes out of scope and is about to be deleted.

Even if there is an exception.

This is a very powerful concept.

Speaking of powerful concepts, does everybody know what RAII is?

RAII
Resource Acquisition Is Initialization

It's a crap name.

Mostly a C++ pattern since C++ is one of the few languages to have a destructor.

RAII

So that means...

an object aquires a resource when created

then gives it back when deleted

RAII

For example...

create a database connection and then close the connection

open a file, and then close the file

RAII

It boils down to needing to undo something whenever you do something. A LIFO queue or stack if you're into compsci terms.

Naive way of opening a file in C


FILE* f = fopen("some_file.txt", "r");
if( f != NULL )
{
  // read the file and do stuff
  fclose(f);
}
          

FILE* f = fopen("some_file.txt", "r");
if( f != NULL )
{
  // read the file and do stuff, what if this throws an exception?
  fclose(f);
}
          

FILE* f = fopen("some_file.txt", "r");
if( f != NULL )
{
  // read the file and do stuff, what if this throws an exception?
  fclose(f); // will this get called?
}
          

FILE* f = fopen("some_file.txt", "r");
if( f != NULL )
{
  // read the file and do stuff, what if this throws an exception?
  fclose(f); // will this get called? No. No it won't.
}
          

Dumbass way of opening a file in Python


f = open("some_file.txt","r")
f.read()
close(f)
        

It looks a lot like the C version.

Slightly less dumbass way of opening a file in Python


try:
  f = open("some_file.txt","r")
  f.read()
finally:
  close(f)
        

This is actually totally safe but verbose and hard to read.

Awesome Pythonic way of opening a file


with open("some_file.txt","r") as f:
  f.read()
  # or throw an exception, we don't care
  # close(f) will get called
        

this more or less discombobulates to the previous example

Invoking a context manager


with a_ctx_mgr():
    # do some stuff
    

Invoking a context manager


with a_ctx_mgr() as var:
    # do some stuff with var
        

you can nest them


with a_ctx_mgr() as var:
    with other_ctx_mgr() as other_var:
        # do some stuff with var and other_var
    

Invoking a context manager

or on the same line


with a_ctx_mgr() as var, with other_ctx_mgr() as other_var:
    # do some stuff with var and other_var
    

Lock Example

Anybody who's every done multithreaded code knows you need to use locks/mutex/semaphores/critical sections/etc

Hopefully you also know that you have to unlock your mutex when you're done.

Lock Example - Lame and unsafe


from threading import Lock
mutex = Lock()

mutex.acquire()
print "is locked: ", mutex.locked()
mutex.release()
print "is locked: ", mutex.locked()
        

is locked:  True
is locked:  False
        

Lock Example - How the cool kids do it


from threading import Lock
mutex = Lock()

with mutex:
  print "is locked: ", mutex.locked()

print "is locked: ", mutex.locked()
        

is locked:  True
is locked:  False
        

You can write your own custom Context Managers

Two methods...

  • decorators - for simple cases
  • classes - for more advanced cases

Decorator method


from contextlib import contextmanager

@contextmanager
def custom_ctx_mgr(*args, **kwargs):
  # setup code
  yield # can return something if desired
  # cleanup code
        

Class method


class custom_ctx_mgr(object):

    def __init__(self, *args, **kwargs):
        """
        this is a normal object and can take initialization parameters
        """

    def __enter__(self):
        """
        the value returned by this method is bound to the `as` variable
        """
        return self # or other object or nothing

    def __exit__( self, exc_type, exc_val, exc_traceback):
        """
        return True to cause caller to continue happily
        return False to cause caller to reraise passed in exception
        """
        

Lets look at some examples

Behold the awesome might of Context Managers!

function is too big and ugly to fit here

Context Managers...

To know them is to love them.

To love them is to know them.

Questions?