Context manager: Difference between revisions
m (→Python) |
m (→Python) |
||
(4 intermediate revisions by the same user not shown) | |||
Line 21: | Line 21: | ||
As things are, e.g.: | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
with open(filename) as file: | with open(filename) as file: | ||
Line 44: | Line 44: | ||
* __exit__() that will be called afterwards | * __exit__() that will be called afterwards | ||
: also if an exception is raised (so is much like a | : also if an exception is raised (so is ''functionally'' much like a try-finally, though not as visible) | ||
Line 52: | Line 52: | ||
If open() didn't already do this {{comment|(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: | If open() didn't already do this {{comment|(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: | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
class | class PrintOpen(object): | ||
def __init__(self, filename): | def __init__(self, filename): | ||
self.filename = filename | self.filename = filename | ||
Line 66: | Line 66: | ||
# Prove to yourself that it works: | # Prove to yourself that it works: | ||
with | with PrintOpen('textfile') as t: | ||
print( t ) | print( t ) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 76: | Line 76: | ||
* the prints are there to signal when each happens | * 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. | Another (often less readable) way to create almost the same (pointless) example is the @contextlib.contextmanager decorator. | ||
This is | This is trickier [[syntax-fu]] (often less readable, but sometimes more succinct), where | ||
* you write a generator, | * you write a generator, | ||
* everything before the yield is effectively run as part of __enter__, and | * everything before the yield is effectively run as part of __enter__, and | ||
* that exactly-one yield is what gets assigned. | * that exactly-one yield is what gets assigned. |
Latest revision as of 10:55, 7 May 2024
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
As things are, e.g.:
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 functionally much like a try-finally, though not as visible)
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 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 (often less readable) way to create almost the same (pointless) example is the @contextlib.contextmanager decorator.
This is trickier syntax-fu (often less readable, but sometimes more succinct), 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#