The reason is because an exception is a runtime check. It caught an error that is hidden until runtime. The better way is to encode this logic into a static type checking system so this error isn't even permitted to run.
With the type Optional[Int], Any other function that utilizes the output type of this function MUST also be an Optional[Int] and not just an Int, meaning that the function signature must handle the None case and this is caught statically before run time. Insofar as the function type signature... python type checkers should handle this error.
However tbh I'm not sure how extensive type checkers for python are and whether or not they can track this type of error for the logic itself. For example while the below will catch a type error if you feed it a bad parameter, the signature itself could be wrong and not caught by a type checker:
x1: Int = 2 # results in type error (Good)
x2: Optional[Int] = None # results in runtime error. This is bad in terms of safety
def addOne(x: Optional[Int]) -> Optional[Int]:
return x + 1
As far as I know the above is permitted by python type checkers when truly correct code is below: def addOne(x: Optional[Int]) -> Optional[Int]:
return x + 1 if x is not None else None
However with python 3.10 pattern matching the level of static type checking can increase to the point where this type of safety is 100% possible: https://www.python.org/dev/peps/pep-0636/. x1: Int = 2 # results in type error (Good)
x2: Optional[Int] = None # No error (also good)
def addOne(x: Optional[Int]) -> Optional[Int]:
match x:
case None: # if this case were deleted should result in static type error (also good)
return None
case _:
return x + 1
Not sure if python type checkers can handle the above code, but exhaustive type checking is 100% viable in the future if the syntax utilizes the pattern matching shown above. Haskell and Rust already have this level of type safety. See: https://rustc-dev-guide.rust-lang.org/pat-exhaustive-checkin...What this means in short is that you are wrong. Whatever your notions of "modern" python should be having division by zero return a runtime error instead of a None or an Error type is definitively worse. We are at a point in our technology that this type of error should be caught automatically BEFORE a program even runs. All type errors should be encoded into types and caught via static type checking and NOT via exceptions.