Exceptions which are explicitly raised in the source code are (in general) only a small fraction of the exceptions that can be raised. It is only a small exaggeration to say that the only line of Python code which cannot raise an arbitrary exception is pass. An exception checker that only considered explicit raise statements would be as useful as a type checker that only tracked the types of variables and functions starting with “Q”.
If all it took to satisfy the type checker was to not raise an exception, they wouldn’t be checked exceptions!
You misunderstand my objection. I’m not saying that people would declare they raise BaseException to defeat the type checker. I’m saying that, in general, they would be forced to in order to satisfy the type checker.
Suppose you call a function that explicitly raises (let’s say) CoffeePotOutOfCoffeeError. Your function can’t deal with that, only the main application function can (by reporting it to the user), so your function should just let the exception pass.
But it can’t, because the type-checker sees you are calling a function that raises CoffeePotOutOfCoffeeError, so you have to satify the type-checker by either catching it and dealing with it (which you can’t do!) or by declaring that your function may raise CoffeePotOutOfCoffeeError.
Now every function that calls your function has to do the same. That CoffeePotOutOfCoffeeError declaration will spread virally from function to function, as every function which calls yours directly or indirectly will need to declare they can implicitly raise it in order to satify the checker.
Unless you catch the exception and throw it away, thus leading to your end-users suffering accute caffeine withdrawal when they try to make a coffee and discover too late that the coffee pot is out of coffee.
And of course its not just CoffeePotOutOfCoffeeError, the same argument applies to every exception which you cannot deal with and would like to just pass through to your function’s caller to deal with (which will be the majority of exceptions). You either declare it, or catch and suppress it. Now that might not literally be BaseException, but it will typically be a pretty large subset of exceptions.
I’m not talking about the case where an object doesn’t define __len__. Of course mypy and other type checkers should be able to recognise that and flag it. I’m talking about objects which do define __len__. In that case that method almost surely could raise anything.
Remember, it’s not just what is explicitly raised inside the method. It is also what could be implicitly raised by any function or expression called inside the method, and anything called by those functions, and so on. E.g. if anything in your method directly or indirectly divides, then your method can raise ZeroDivisionError.
You have my sympathy, but I think you have the wrong solution.
We know from the experience of Java, and the refusal of many, many smart language designers to follow in Java’s footsteps and introduce checked exceptions, that static checking of exceptions is an anti-pattern.
We also know that the right way to find out what undocumented exceptions can be raised is by extensive testing. Tests, tests and more tests.
If you disagree (as is your right of course) I think you need to demonstrate practical examples of languages where checked exceptions work well, without the contraversy of Java checked exceptions. What do these languages do differently?