On 2021-09-30 4:15 a.m., Steven D'Aprano wrote:
> On Thu, Sep 30, 2021 at 12:03:37AM -0300, Soni L. wrote:
>
> > > Only some.user_code is guarded by the try block. If it turns out that 
> > > code_we_assume_is_safe is not actually safe, and fails with an 
> > > exception, it won't be caught by the try block and you will know about 
> > > it.
> > 
> > Except no, because ExceptionWeCareAbout is part of the public API.
> [...]
>
> You have not convinced me that I have misunderstood the proposal. As I 
> said, some better, runnable code might help. But for the sake of the 
> argument, suppose I have misunderstood and your analysis is correct..
>
> You have just demonstrated that your proposed syntax hurts 
> readability. In your original function, it is easy to recognise 
> potentially poor exception hygiene at a glance:
>
> "Too much stuff inside a try block == potential bad hygiene"
>
> With your proposed syntactic sugar, there is no visible try block, and 
> it is exceedingly unclear which parts of the function body are protected 
> by an implicit try block, and which parts will have the exception caught 
> and turned into RuntimeError, and which parts will have the exception 
> caught and re-raised.

You misnderstand exception hygiene. It isn't about "do the least stuff
in try blocks", but about "don't shadow unrelated exceptions into your
public API".

For example, generators don't allow you to manually raise StopIteration
anymore:

>>> next((next(iter([])) for x in [1, 2, 3]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <genexpr>
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration

This is a (limited) form of exception hygiene. Can we generalize it? Can
we do better about it? This effectively means all generators *are*
wrapped in a try/except, so your point about "too much stuff inside a
try block" goes directly against accepted practice and even existing
python features as they're implemented.

>
> > [no comments on the rest of your points because they're all based on
> > this core misunderstanding.]
>
> The rest of my post is more important.
>
> My comments asking how the compiler is supposed to know which part of 
> the code needs to be guarded with a "re-raise the exception" flag still 
> apply, regardless of whether I have misunderstood your API or not.
>
> Your syntax has:
>
>     def a_potentially_recursive_function(some, args) with 
> ExceptionWeCareAbout:
>         some.user_code()
>         code_we_assume_is_safe()
>         if args.something and some_condition:
>             raise ExceptionWeCareAbout  # Line (A)
>
> How does the compiler know that *only* ExceptionWeCareAbout originating 
> in Line (A) should be re-raised, and any other location turned into 
> RuntimeError?

Same way Rust decides whether to propagate or unwrap a Result: you
*must* tell the compiler.

>
> What if I factor out those last two lines and make it:
>
>     def a_potentially_recursive_function(some, args) with 
> ExceptionWeCareAbout:
>         some.user_code()
>         code_we_assume_is_safe()
>         check_condition_or_raise(args.something, some_condition)
>
> How does the compiler decide to re-raise exceptions originating in the 
> last line but not the first two?

In this case, it explicitly doesn't. You explicitly told it the last
line doesn't raise any exceptions that contribute to your API's
exception surface.

You *must* use try: check_condition_or_raise(args.something,
some_condition) except ExceptionWeCareAbout: raise

(Verbosity can be improved if this feature gets widely used, but it's
beside the point.)

>
> What if I use a pre-prepared exception instance, or an alias, or both?
>
> BadThing = ExceptionWeCareAbout
>
> ERROR = BadThing("a thing happened")
>
>     def a_potentially_recursive_function(some, args) with 
> ExceptionWeCareAbout:
>         some.user_code()
>         code_we_assume_is_safe()
>         if args.something and some_condition:
>             raise ERROR
>
> Your proposal doesn't make it clear how the compiler decides which parts 
> of the body should allow the exception through and which should 
> re-raise.
>
>
>

This works fine because any explicit raise will always poke through the
generated try/except.
_______________________________________________
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/K27TNY6H5SHGNIBV3GC4PRAH2ANLO6ZL/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to