login | login/register with OpenId | register | help
Python function annotations meta-framework


Estimated time: 10 hours

PEP 3107's Mistake

PEP 3107 introduced function annotations for Python 3.x. See the PEP here:

http://www.python.org/dev/peps/pep-3107/

However, there are two huge flaws with the creation of this facility, IMO. The problem only arises when you consider that at some point in the future, there may be more than one use case for function annotations (as the PEP suggests). For example, let's say that in my code, I use function annotations both for documentation and for optional run-time type checking. If I have a framework that expects all the annotations on my function definition to be docstrings, and another framework that expects all the annotations to be classes, how do I annotate my function with both documentation and type checks?

Basically, there is a lack of a standard for layering function annotations.

Is this really a problem?

It's true that some standard for this could organically form in the community. For example, one could imagine tuples being used for this. If an annotation expression is a tuple, than every framework should iterate through the items of the tuple until they find an item of the matching type. However, this won't always work: what if two frameworks are both expecting strings, or two frameworks are both expecting classes, with different semantics?

If we used dicts, this could be avoided since you could do something like this:

def foo(
    *args: {"doc": "arguments", "type": list}, 
    **kwargs: {"doc": "keyword arguments", "type": dict}): \
-> {"doc": "a bar instance", "type": Bar}

but isn't this getting very ugly, very quickly?

Function Annotation Syntax

An aside: my personal opinion is that the function annotation syntax is very, very ugly, and will quickly clutter function definitions so as to make them totally unreadable. If you compare how annotations would look using the PEP 3107 syntax to the kinds of 'annotation decorators' you find in the Pyanno project, you can see exactly what I mean.

http://www.fightingquaker.com/pyanno/

My Proposal

What I'm proposing for this clusterify project is to work on a PEP 3107 "meta-framework". That is, a module that provides a set of functions, classes, and possibly decorators that make using the function annotations support provided in PEP 3107 much easier, and making it possible for people to write function annotation processors that will follow conventions for annotation layering, working around the problem described above by following some basic conventions.

Here one idea, but I'd like to hear others. Still very back of the envelope:

We could work around PEP 3107's clutter-filled syntax by mostly ignoring it. For example, let's say we created a class called "Annotation" and a corresponding "ann" function. Annotations could be declared thusly:

 foo_args = Annotation(std_type_check=list, doc="arguments")
 foo_kwargs = Annotation(std_type_check=dict, doc="keyword arguments
 foo_return = Annotation(std_type_check=Bar, doc="a bar instance)

 def foo(*args: foo_args, **kwargs: foo_kwargs) -> foo_return:

 del foo_args, foo_kwargs, foo_return

 # or

 def foo(*args: ann(std_type_check=list, doc="arguments"),
         **kwargs: ann(std_type_check=list, doc="keyword arguments")) \
         -> ann(std_type_check=Bar, doc="a Bar instance"):

The keyword arguments that the Annotation class could take could be registered dynamically by frameworks. For example, if you import foo.stdtypecheck, then std_type_check becomes available. This is due to a registration mechanism. Of course, these aren't very heavily namespaced, but they could be if we were to tweak the design a bit.

We could also maintain a Wiki where we describe the standard annotation names that are already taken.

An alternative to this is to make Annotation an ABC, see PEP 3119:

http://www.python.org/dev/peps/pep-3119/

Annotation implementors could then implement their own Annotation types, which could then be taken as arguments to the 'ann' function. e.g.

from typechecks import StdTypeCheck
from runtimedocumentation import Documentation

 def foo(*args: ann(StdTypeCheck(list), Documentation("arguments"),
         **kwargs: ann(StdTypeCheck(list), Documentation("arguments")) \
         -> ann(StdTypeCheck(Bar), Documentation("a Bar instance")):

A final option would be to really ignore function annotation syntax and simply write decorator versions of these that modify the func_annotations dict specified in the PEP.

Tools and Technologies

  • Python, obviously :-)
  • I'm fine with using any SCM: SVN, git, etc.
  • I have a server at my disposal where I could set up a trac instance if this warranted it
  • I'll have free time to start design work on this immediately

Would love to hear any feedback.

Comments
Project author and members receive automatic email notifications when comments are posted.

I clearly didn't use Markdown properly to produce the autolinks. Here:

PEP 3107: Function Annotations http://www.python.org/dev/peps/pep-3107/

Pyanno: http://www.fightingquaker.com/pyanno/

PEP 3119: ABCs http://www.python.org/dev/peps/pep-3119/


You could add a comment if you were logged in.