[Python-ideas] Generator-based context managers can't skip __exit__
Steven D'Aprano
steve at pearwood.info
Sun Nov 6 03:07:12 EST 2016
On Sun, Nov 06, 2016 at 06:46:40AM +0200, Ram Rachum wrote:
> Hi everyone,
>
> Here is a simplification of a problem that's been happening in my code:
>
> import contextlib
>
> @contextlib.contextmanager
> def f():
> print('1')
> try:
> yield
> finally:
> print('2')
>
>
> g = f()
> g.__enter__()
>
>
> This code prints 1 and then 2, not just 1 like you might expect.
I expect it to print 2. After all, its in a finally clause.
And why are you calling g.__enter__() directly? Calling dunder methods
by hand is nearly always the wrong thing to do.
> This is
> because when the generator is garbage-collected, it gets `GeneratorExit`
> sent to it.
Right. That's what they're designed to do.
Later, in another thread you say:
"Well, you think it's weird that I want a `finally` clause to not be
called in some circumstances. Do you think it's equally weird to want an
`__exit__` method that is not called in some circumstances?"
Yes to both. It is seriously weird.
You might as well be complaining that Python calls your __iter__
method when you call iter(my_instance). That's the whole point of
__iter__, and the whole point of finally clauses and the __exit__ method
is that they are unconditionally called, always, when you leave the
with block.
But judging from your code above, it looks like you're not even using a
with block. In that case, instead of abusing the __enter__ and __exit__
methods, why not just create a class with non-dunder enter() and exit()
methods and call them by hand?
g = f() # implementation of f is left as an exercise
g.enter()
if condition:
g.exit()
I'm having a lot of difficulty in understanding your use-case here, and
so maybe I've completely misunderstood something.
> This has been a problem in my code since in some instances, I tell a
> context manager not to do its `__exit__` function. (I do this by using
> `ExitStack.pop_all()`. However the `__exit__` is still called here.
Have you considered something like:
def f():
print('1')
try:
yield
finally:
if f.closing:
print('2')
You can then write a decorator to set f.closing to True or False as
needed. But again, I don't understand why you would want this feature,
or how you are using it, so I might have this completely wrong.
--
Steve
More information about the Python-ideas
mailing list