A major difference from Python is that in Ada, exceptions are not data types. The only data you can associate with an instance of an exception is a String:
raise My_Exception with "some string";
So when catching an exception, we can only ever know which kind of exception got thrown, and get some String which may be useful for logging, but not so much for programmatically analyzing what went wrong in more detail.
This also means that we don't have a way to chain exceptions, like it is often done e.g. in Java, where an exception is often wrapped inside another exception to hide details while still being able to see in the stack trace what exactly went wrong.
The reason for this is that the language has been designed for exceptions to be used scarcely. Unlike Python, Ada has been designed to target bare metal, where you may want to disable runtime checks for performance reasons. If you do that, you won't get the Constraint_Error
on division by zero and the program will have undefined behavior. So using exceptions influences code portability negatively.
Some compilers provide additional options, like the No_Exception_Propagation
restriction in GNAT, which allows exception only if they are caught within the same subroutine.
Still, exceptions are a useful tool for communicating failure without completely obstructing normal program flow in the code (you will know what I mean if you've ever written Go, or C). But generally, the Ada Style Guide advises that
Exceptions should be used as part of an abstraction to indicate error conditions that the abstraction is unable to prevent or correct. Because the abstraction is unable to correct such an error, it must report the error to the user.
This means that, if you do have the possibility to correct a problem without using an exception, you should do that, because it should be part of the normal, non-exceptional, program flow.