On Sat, Jun 22, 2019 at 12:14:02PM -0400, James Lu wrote:
> If a function that tends to return a context manager returns None,
> that should not mean an error occurred.
I suppose that *technically* this is true. It might be designed to
return a context manager or None. But:
1. Because of the way Python returns None from functions when you fall
out the end without an explicit return, "returns None is a programming
error (a bug)" is the safe way to bet.
2. If it is intentional, and documented, the usual practice is to
explicitly check for the exceptional value:
mo = re.match(a, b)
if mo:
print(mo.group())
We don't give None a .group() method that returns None, so that lazy
coders can write this and suppress the exception:
assert hasattr(None, "group")
print(re.match(a, b).group()) # doesn't raise any more, yay!
because there are too many ways that None *is* an error and we don't
want to cover them up. Getting an exception if you have an unexpected
None is a *good thing*.
So I don't think that with blocks should just automatically skip running
None. For every context manager that intentionally returns None, there
are probably a hundred that do it accidentally, where it is a bug.
I can see at least three ways to deal with the very few "context manager
or None" cases, that require no changes to the language:
1. Have the context manager explicitly return None and require the
caller to explicitly check for it:
x = Context(arg)
if x is not None:
with x:
...
This adds two lines and one extra level of indentation, which is not too
bad for something self-documenting and explicit. In Python 3.8 we can
reduce it to one line and one extra level of indentation:
if (x := Context(arg)) is not None:
with x:
...
which might be the best solution of all.
2. Have the context manager raise an exception (not an error!) and
require the caller to explicitly catch it:
try:
with Context(arg) as x:
...
except ExceptionalCase:
pass
This adds three lines and one extra level of indentation. On the other
hand, if your context manager can raise on actual errors as well, this
is the most natural way to solve the problem:
try:
with Context(arg) as x:
...
except SomethingBadError as err:
handle(err)
except ExceptionalCase:
pass
3. Write the context manager to return a do-nothing mock-up instead of
None. You might be able to use the Mock object in the standard library
for this (I have never used Mock, so I don't know if it is suitable) but
worst case it adds a one-off cost to the writer of the context manager,
but it gives the caller do-nothing handling for free:
with ContextOrMock(arg) as x:
...
If x happens to be a do-nothing mock, the code in the block will do
nothing.
> If an error or unexpected
> condition occurred, an exception should be thrown. Errors and
> exceptions should result in the code within the with statement not
> executing.
Isn't that how the with statement already works?
> We could add a new constant to Python, “Dont.” It’s a global falsey
> Singleton and a context manager that causes the code inside “with” not
> to execute.
Don't what? I might be more sympathetic to that if the name was more
descriptive. Say, "SkipWithBlock", and we make it an exception.
To skip the with-block, have the context manager raise SkipWithBlock
from inside its __enter__ method.
This is less disruptive because:
- it won't hide the exception from buggy context managers that
accidentally return None;
- it requires an explicit raise inside the context manager;
- although it adds a new built-in, it is an exception, and
psychologically people tend to think of exceptions as seperate
from the builtins (at least *I* do, and people I've spoken to).
All this supposes that there is a moderately common need for a context
manager to skip the with block, and that the existing solutions aren't
sufficient.
--
Steven
___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/python-ideas@python.org/message/4XSBZO5BCZRQVUKN5SYJZOL5LJGI4HWX/
Code of Conduct: http://python.org/psf/codeofconduct/