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
For example:
with open(filename) as file:
file_data = file.read()
is roughly equivalent to
try:
file = open(filename)
file_data = file.read()
finally:
file.close()
...because the built-in open() already implements the interface we're about to describe (and e.g. does a close in its __exit__).
At its core, the with statement takes an object; that object's has a
- __enter__() that 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 much like a try-finally)
If open() didn't already do this (as it is, this example is redundant and pointless. A better example would be a database connection, but it'd take a few more lines to actually be runnable code), we might add it like:
class Test(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 Test('textfile') as t:
print( t )
Notes:
- __init__ isn't part of the concept, but is how you get parameters in
- (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 (often less readable) way to create almost the same (pointless) example is the @contextlib.contextmanager decorator.
This is further trickery 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.
@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 second 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#