[Python-3000] Conventions for annotation consumers (was: Re: Draft pre-PEP: function annotations)

Collin Winter collinw at gmail.com
Mon Aug 14 23:59:38 CEST 2006

On 8/14/06, Paul Prescod <paul at prescod.net> wrote:
> Third, we absolutely need a standard for
> multiple independent annotations on a parameter. Using lists is a
> no-brainer. So let's do that.

The problem with using lists is that its impossible for non-decorator
annotation consumers to know which element "belongs" to them.

Way back in http://mail.python.org/pipermail/python-3000/2006-August/002865.html,
Nick Coghlan said:
> However, what we're really talking about here is a scenario where you're
> defining your *own* custom annotation processor: you want the first part of
> the tuple in the expression handled by the type processing library, and the
> second part handled by the docstring processing library.
> Which says to me that the right solution is for the annotation to be split up
> into its constituent parts before the libraries ever see it.
> This could be done as Collin suggests by tampering with
> __signature__.annotations before calling each decorator, but I think it is
> cleaner to do it by defining a particular signature for decorators that are
> intended to process annotations.
> Specifically, such decorators should accept a separate dictionary to use in
> preference to the annotations on the function itself:
>    process_function_annotations(f, annotations=None):
>      # Process the function f
>      # If annotations is not None, use it
>      # otherwise, get the annotations from f.__signature__

I've come to like this idea more and more. Here's my stab at a
dict-based convention for specifying annotations for decorator-style

There are several annotation consumers, docstring, typecheck and
constrain_values. Respectively, these treat annotations as
documentation; as restrictions on the type of an argument; as
restrictions on the values of an argument.

Each of these is defined something like

def consumer(annotated_function, annotations=sentinel):

If the consumer isn't given an `annotations` parameter, it is free to
assume it is the only consumer for the annotations on that function
and is free to treat the annotation expressions however it sees fit.
However, if it is given an `annotations` argument, it should observe
those annotations and only those annotations.

The more complete example:

@multiple_annotations(docstring, typecheck, constrain_values)
def foo(a: {'docstring': "Frobnication count",
            'typecheck': Number,
            'constrain_values': range(3, 9)},
        b: {'typecheck': Number,
             # This can be only 4, 8 or 12
            'constrain_values': [4, 8, 12]}) -> {'typecheck': Number}

Here, multiple_annotations assumes that the annotation dicts are keyed
on consumer.__name__; the test "if consumer.__name__ in
per_parameter_annotations" should do nicely for figuring out whether a
given consumer should be provided an `annotations` argument. (It is up
to multiple_annotations() to decide whether "consumer.__name__ in
per_parameter_annotations == False" should raise an exception.)

Collin Winter

More information about the Python-3000 mailing list