On Fri, 25 Sep 2020 at 20:36, Chris Angelico
On Sat, Sep 26, 2020 at 5:27 AM Oscar Benjamin
wrote: On Fri, 25 Sep 2020 at 18:59, Chris Angelico
wrote: On Sat, Sep 26, 2020 at 2:32 AM Oscar Benjamin
wrote: I do agree but maybe that suggests a different role for annotated exceptions in Python. Rather than attempting to enumerate all possible exceptions annotations could be used to document in a statically analysable way what the "expected" exceptions are. A type checker could use those to check whether a caller is handling the *expected* exceptions rather than to verify that the list of *all* exceptions possibly raised is exhaustive.
Consider an example:
def inverse(M: Matrix) -> Matrix: raises(NotInvertibleError) if determinant(M) == 0: raise NotInvertibleError rows, cols = M.shape for i in range(rows): for j in range(cols): ...
Here the function is expected to raise NotInvertibleError for some inputs.
Please no. Once again, this will encourage "handling" of errors by suppressing them. Don't have anything that forces people to handle exceptions they can't handle.
Why would anyone be forced to "handle" the exceptions?
The suggestion (which you've snipped away) was not that it would be a type-checking error to call the inverse function without catching the exception. It only becomes a type-checking error if a function calls the inverse function and claims not to raise the exception. The checker just verifies the consistency of the different claims about what is raised.
Forcing people to declare it is exactly the same as forcing them to handle it every other way. There are many MANY bad ways this can go, most of which have been mentioned already:
I wouldn't suggest that a declaration be forced but rather that a false declaration be disallowed. More below...
1) Catching exceptions and doing nothing, just so your linter shuts up 2) Catching exceptions and raising a generic "something went wrong" error so that's the only thing you have to declare 3) Declaring that you could raise BaseException, thus adding useless and meaningless boilerplate to your functions 4) Redeclaring every exception all the way up the chain, and having to edit them every time something changes 5) Copying and pasting a gigantic list of exceptions onto every function's signature
I'm sure those things would happen sometimes. At least all of these things are very visible in the diff so a reviewer can at least see what's going on. The tricky thing with exceptions is the action at a distance effect so that it's not always obvious what the effect of a change is so when you see a diff like: - x = f() + x = g() you would potentially need to trawl a lot of code to see how that affects what exceptions might be raised.
And what are the possible *good* ways to use this? What do you actually gain? So far, the only use-case I've heard is "IDEs might help you tab-complete an except clause". That's only going to be useful if the exception tracking is accurate all the way up and down the tree, and that's only going to happen if it's done by analysis, NOT by forcing people to declare them.
I can tell you what I would use a feature like this for (if it is implemented in a useful way) which is cleaning up the exception handling in the sympy codebase. It's a large codebase with thousands of raises and excepts: $ git grep raise | wc -l 8186 $ git grep except | wc -l 1657 There are many common bugs that arise as a result of exceptions that are overlooked. There are also often simple ways to rewrite the code so that it becomes exception free. For example in sympy: if x.is_positive: # never raises (excepting bugs) - False if indeterminate if x > 0: # raises if indeterminate That distinction is not understood by many contributors to sympy but is easy to explain during code review. That's an easy example but others are not so easy.
I've worked with Java's checked exceptions, which is what you're getting at here. They're terrible. The best way to handle them is to ensure that all your exceptions are RuntimeErrors so they don't get checked by the compiler.
My understanding of the situation in Java although I don't have much experience of it as that checked exceptions must *always* be declared so e.g. (in hypothetical Python syntax): def f(): raises FooError ... def g(): f() # <--- typecheck error because g does not declare FooError That is *not* what I am suggesting. Rather only an explicit raises hint could give an error so the above is fine. The type checker knows that g might raise FooError but there's nothing wrong with raising an error without any declaration. However the type checker *would* complain about this: def f(): raises FooError ... def g(): raises None f() # <--- typecheck error because g claims that it does not raise I'm sure that there would be some cases of the problems you describe (as there always will be around exceptions). However I don't think it would need to work out the way that it does in Java. Oscar