Skip to content

Annotating functions which don't raise exceptions #604

Description

@pfalcon

I would find it interesting to be able to annotate functions which never throw exceptions. "Never" can be either in the absolute sense, or in the sense of "checked exceptions", with some subset of exception designated as "unchecked" (details of this designation are perhaps up to the actual tool to process annotations).

Example of a function which absolutely never raises exceptions (well, at least per language semantics, particular implementations might still have means to break with some implementation-related exceptions):

def foo(l):
    if isinstance(l, list) and len(l) > 0:
        return l[0]

Example of a function which may throw (unchecked) exception if API contract is violated:

def foo(l: list):
    if len(l) > 0:
        return l[0]

So, hopefully these examples show that the notion does exist in Python.

Now the question how to annotate it. Following the existing NoReturn, it would be called NoRaise or NoThrow. "nothrow" terminology if familiar from C++ (and it seems to be replaced even there), so perhaps sticking with native Python terminology makes sense (but "throw" is native to Python too, re: exception handling with generators).

More interesting question is where to put that annotation. Taking the example above, a natural annotation would be:

def foo(l) -> Optional(Any), NoRaise:

And I was quite surprised that the usual "implicit tuple" syntax rule doesn't apply here, and the above is SyntaxError as of CPy3.7:

Python 3.7.1 (default, Oct 22 2018, 11:21:55) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo() -> list, int:
  File "<stdin>", line 1
    def foo() -> list, int:
                     ^
SyntaxError: invalid syntax

I wonder if that warrants a separate issue report. And whether it was a cunning design choice mere mortals need to decipher, because that seems too obvious thing to overlook with an implicit tuple syntax, again (neither problematic from forma grammar perspective, even in LL it's "->" expr ("," expr)* ":", which is trivial, Python has more complex grammar rules in many places.

So, all in all, now it would need to be written as:

def foo(l) -> (Optional(Any), NoRaise):

Which is of course not as pretty as without parens.

Activity

  1. pfalcon commented on Jan 7, 2019

    @pfalcon
    Author

    This is related to #71 . In a sense, it's complement of it: there, people want to annotate which exceptions are thrown. Supposedly, when these annotations are full and complete, functions which aren't annotated to raise any exception, are those which don't raise them. In reality of course, that level of coverage is not realistically achievable, so instead of explicitly marking all exceptions raised, thin RFC proposes to allow to explicitly mark those functions which don't raise exceptions.
    More discussion of this approach: #71 (comment)

  2. pfalcon commented on Jan 7, 2019

    @pfalcon
    Author

    I had this idea in mind for some time, and the discussion in https://mail.python.org/pipermail/python-dev/2019-January/155998.html only gives an example that "nothrow" property is important to the Python language, whether those in powers of it admit, or not.

    To recap, the talk in that thread (and with reference to the official docs at https://docs.python.org/3/reference/compound_stmts.html#try) is that

    except E as N:
        body
    

    is compiled as

    except E as N:
        try:
            body
        finally:
            N = None
            del N
    

    The reason why there're 2 statements "N = None; del N", is exactly for that (compiler-generated) code to have no-throw property - even if "body" (containing arbitrary user code) has del N statement itself.

    But the reason the exception handler body needs to be wrapped into extra try-finally in the first place, is from the reservations that it may throw exceptions! If we'd know that it doesn't, we could avoid that overhead. And note that it doesn't even apply to "native code generation", but to bytecode generation, as done e.g. by CPython.

    Of course, I don't call for CPython itself to do such an optimization. But there're many Python language implementations which try to advance performance state of the start for the language, and setting a common ground for them to annotate nothrow/NoRaise functions is worthy a task IMHO. With that idea I submit this ticket.

  3. ilevkivskyi commented on Jan 7, 2019

    @ilevkivskyi
    Member

    I would find it interesting to be able to annotate functions which never throw exceptions.

    What exactly do you want? Do you just want to have a syntax to attach some extra info to (return) types that would be ignored by type checkers but may be used by some other tools? If yes, then this is essentially a duplicate of #600. With that you can write:

    def func() -> Annotated[int, NoRaise]:
        ...
  4. pfalcon commented on Jan 9, 2019

    @pfalcon
    Author

    Thanks for reference to #600. I guess I could crash there with alternative syntax ideas ;-).

    Beyond syntax for multiple annotations, it's also about choosing a specific annotation symbol for "no-throw" case. (I understand it unlikely to be added to "typing" module any type soon, so question is "if it would be added, what would it be", and python/typing is a kind of crossroads among different annotations projects, that's why I posted it here).

  5. ilevkivskyi commented on Jan 10, 2019

    @ilevkivskyi
    Member

    The choice of name is up to the team/tool that is going to support it, other type checkers will just ignore it. We can keep this open for a while to see who else is interested in this feature.

  6. SemMulder commented on Jun 11, 2019

    @SemMulder

    +1

  7. rmzr7 commented on Aug 9, 2021

    @rmzr7

    Perhaps an approach to consider interesting approach is similar to what Swift does

    def can_throw_errors(): ThrowsError[String]

  8. added
    topic: featureDiscussions about new features for Python's type annotations
    on Nov 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions