Hi!

To help automatic document generators and static type checkers reason more about the code, the possible side-effects and raised exceptions could also be annotated in a standardized way.

I'll let some example code talk on my behalf. Here's a simple decorator for annotating exceptions:
In [1]: import typing as t

In [2]: def raises(exc: Exception) -> t.Callable:
   ...:     def decorator(fn: t.Callable) -> t.Callable:
   ...:         fn.__annotations__["__raises__"] = exc
   ...:         return fn
   ...:     return decorator
   ...:

In [3]: @raises(ValueError)
   ...: def hello_if_5(x: int) -> None:
   ...:     if x != 5:
   ...:         raise ValueError("number other than 5 given")
   ...:     print("Hello!")
   ...:

In [4]: hello_if_5.__annotations__
Out[4]: {'x': int, 'return': None, '__raises__': ValueError}

In [5]: hello_if_5(1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-7fb1d79ce3f4> in <module>
----> 1 hello_if_5(1)

<ipython-input-3-1084d197ce1b> in hello_if_5(x)
      2 def hello_if_5(x: int) -> None:
      3     if x != 5:
----> 4         raise ValueError("number other than 5 given")
      5     print("Hello!")
      6

ValueError: number other than 5 given

In [6]: hello_if_5(5)
Hello!

In [7]:

and here's a simple decorator for annotating side-effects:

In [1]: import typing as t

In [2]: def side_effect(has_side_effect: bool) -> t.Callable:
   ...:     def decorator(fn: t.Callable) -> t.Callable:
   ...:         fn.__annotations__["__side_effect__"] = has_side_effect
   ...:         return fn
   ...:     return decorator
   ...:

In [3]: a = 10

In [4]: @side_effect(True)
   ...: def change_a(val: int) -> None:
   ...:     global a
   ...:     a = val
   ...:

In [5]: change_a.__annotations__
Out[5]: {'val': int, 'return': None, '__side_effect__': True}

In [6]: change_a(100)

In [7]: a
Out[7]: 100

In [8]: @side_effect(True)
   ...: def mutate_list(items: t.List) -> None:
   ...:     items.append("new item")
   ...:

In [9]: mutate_list.__annotations__
Out[9]: {'items': typing.List, 'return': None, '__side_effect__': True}

In [10]: l = ["old item"]

In [11]: mutate_list(l)

In [12]: l
Out[12]: ['old item', 'new item']

In [13]:

The example implementations have some obvious limitations, such as only allowing one error type. What do you think of the idea in general? Do you feel this is something that could be included in Python? typing would probably be a good module to store such decorators, I guess…

Miikka Salminen
miikka.salminen@gmail.com