On Sat, Sep 26, 2020 at 8:50 AM Sascha Schlemmer via Python-ideas < [email protected]> wrote:
> I think a clear distinction has to be made between errors that belong to > the api surface of a function e.g. some `ValueError` or `ZeroDivisionError` > and *exceptional* errors e,g.`KeyboardInterrupt` or > `DatabaseExplodedError`. For the latter case exceptions work perfectly well > as they are and it is both unhelpful and infeasible to annotate/document > every exception - just let the exception bubble up and someone will handle > it (might even be a process supervisor like supervisord) > > For the first case I think a better way to model a function, that has some > *expected* failure mode is to try and write a *total* function that doesn’t > use exceptions at all. Let me show what I mean: > > ``` > def div(a: int, b: int) -> float: > return a / b > ``` > > This function tries to divide two integers, trouble is that `div` is not > defined for every input we may pass it, i.e. passing `b=0` will lead to > `ZeroDivisionError` being raised. In other words `div` is a *partial* > function because it is not defined for every member of its domain (type of > its arguments). > > There are two ways to amend this issue and make `div` a *total* function > that is honest about its domain and co-domain (types of input arguments and > return type): > > 1. Extending the type of the functions co-domain (return type) and making > the error case explicit (see > https://returns.readthedocs.io/en/latest/index.html for a library that > implements this style of error handling) > > ``` > def div(a: int, b: int) -> Maybe[float]: > try: > return Some(a / b) > except ZeroDivisionError: > return Nothing() > ``` > > or > > ``` > def div(a: int, b: int) -> Result[float, ZeroDivisionError]: > try: > return Success(a / b) > except ZeroDivisionError as error: > return Failure(error) > ``` > > Now `div` does return a valid instance of `Maybe` (or `Result` if more a > more detailed failure case is wanted) which is either something like > `Some(3.1415) ` or `Nothing` (analogous to `None` ). The caller of the > function then has to deal with this and mypy will correctly warn if the > user fails to do so properly e.g. > > ´´´ > div(1,1) + 1 # mypy will give a type error > ´´´ > https://en.wikipedia.org/wiki/Partial_function https://en.wikipedia.org/wiki/Partial_application#Implementations - https://docs.python.org/3/library/functools.html#functools.partial - https://docs.python.org/3/library/functools.html#functools.singledispatch PEP 0622: Structural Pattern Matching https://www.python.org/dev/peps/pep-0622/ > > 2. Restricting the functions domain to values with defined behavior > > ``` > def div(a: int, b: NonZeroInteger) -> float: > return a / b > ``` > > In this case the burden is put on the caller to supply valid inputs to the > function and this effectively pushes the error handling/ input validation > out of `div` > https://pypi.org/project/icontract/ : ```python >>> import icontract >>> @icontract.require(lambda x: x > 3) ... def some_func(x: int, y: int = 5)->None: ... pass ... >>> some_func(x=5) # Pre-condition violation >>> some_func(x=1) Traceback (most recent call last): ... icontract.errors.ViolationError: File <doctest README.rst[1]>, line 1 in <module>: x > 3: x was 1 ``` There exist a couple of contract libraries. However, at the time of this > writing (September 2018), they all required the programmer either to learn > a new syntax (PyContracts) or to write redundant condition descriptions ( > e.g., contracts, covenant, dpcontracts, pyadbc and pcd). https://andreacensi.github.io/contracts/ supports runtime argument and return constraints as Python 3 annotations, with the @contract decorator, and in docstrings: ```python @contract def my_function(a : 'int,>0', b : 'list[N],N>0') -> 'list[N]': # Requires b to be a nonempty list, and the return # value to have the same length. ... ``` icontract supports inheritance. Defining the domain and range of a partial callable is relevant to this discussion about callables that don't return a value or None, per se, but instead raise Exceptions. Results looks neat. I think that just reading about all of these tools could make me a better programmer. What an ironic conflation of None with null void pointer references here (see also Pandas' handling of None/np.NaN) https://returns.readthedocs.io/en/latest/index.html#id1 : > Or you can use Maybe container! It consists of Some and Nothing types, representing existing state and empty (instead of None) state respectively. > > A language that does all this really well is F# (which like python is a > multi-paradigm language that offers both object-oriented and > functional-programming-oriented constructs). The trouble in python at the > moment is that using something like `Maybe` and `Result` is not as nice to > use as exceptions due to lack of a nice syntax i.e. for exceptions we have > `try`/`except` and for functional error handling we’d need pattern matching. > Do `Maybe` and `Result` break duck typing? The 'returns' docs link to: https://sobolevn.me/2019/02/python-exceptions-considered-an-antipattern Something other than 'returns' would be easier to search/grep for.
_______________________________________________ Python-ideas mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/[email protected]/message/KIILNBXHIQJPYCIHFFA2Y2FNQXS5YO2D/ Code of Conduct: http://python.org/psf/codeofconduct/
