Context manager
Context managers are syntactic sugar you use on a code block, to bolt in some implied extra code.
They seem largely used to do resource cleanup more implicitly and automatically,
but there are various other uses.
Python
Because the built-in open() already implements the interface we're about to describe (and e.g. does a close() in its __exit__), a context manager such as:
with open(filename) as file:
file_data = file.read()
works out as roughly equivalent to
try:
file = open(filename)
file_data = file.read()
finally:
file.close()
The syntactic sugar's actual workings:
- the with statement takes an object which should have an
- __enter__() will be called at the start of the block, and returns the thing that will be assigned
- __exit__() that will be called afterwards -- also if an exception is raised (so is functionally much like a try-finally, though not as visibly)
The example below is mostly redundant and pointless, because again, open() already provides a context manager that closes on exit, but it's a simple-to-understand code fragment. (A better example would be a database connection, but it'd take a few more lines to actually be runnable code):
class PrintOpen(object):
def __init__(self, filename):
self.filename = filename
def __enter__(self):
print('__enter__')
self.fob = open(self.filename)
return self.fob
def __exit__(self, exc_type,exc_value, exc_traceback):
print('__exit__')
self.fob.close()
# Prove to yourself that it works:
with PrintOpen('textfile') as t:
print( t )
Notes:
- __init__ isn't part of the concept, but is how you get parameters into an object to use later
- ...without which you'll find it hard to get __enter__ to return something useful to you
- the prints are there to signal when each happens
---
Another way to create almost the same (pointless) example is the @contextlib.contextmanager decorator.
This is trickier syntax-fu, and often less readable, but sometimes much shorter, where
- you write a generator,
- everything before the yield is effectively run as part of __enter__, and
- that exactly-one yield is what gets assigned.
For example, to do the same redundant open-and-close again in this style:
@contextlib.contextmanager
def test(filename): #
f = open(filename)
try:
yield f
finally:
f.close()
with test('textfile') as t:
print( t )
Javascript
Nothing built in, but because ES6 has generators(good support since ~2017) you can insert both some before and after code, much like that later python example.
To copy-paste from [1]:
function* usingGroup() {
console.group()
try {
yield;
} finally {
console.groupEnd();
}
}
for(const _ of usingGroup()) {
console.log('inside a group')
}
The same page also mentions a library to make this a little easier, imitating python.
C#