[Python-3000] Draft pre-PEP: function annotations

Nick Coghlan ncoghlan at gmail.com
Sat Aug 12 09:58:08 CEST 2006


Phillip J. Eby wrote:
> At 03:39 PM 8/12/2006 -0700, Talin <talin at acm.org> wrote:
>> So programmer C, who wants to incorporate both A and B's work into his
>> program, has a dilemma - each has a sharing mechanism, but the sharing
>> mechanisms are different and incompatible. So he is unable to apply both
>> A-type and B-type metadata to any given signature.
> 
> Not at all.  A and B need only use overloadable functions,

Stop right there. "A and B need only use overloadable functions"? That sounds 
an awful lot like placing a constraint on the way annotation libraries are 
implemented in order to facilitate a single program using multiple annotation 
libraries - which is exactly what Talin is saying is needed!

Talin is saying "the annotation PEP needs to recommend a mechanism that allows 
a single program to use multiple annotation libraries". And you're saying "a 
good mechanism for allow a program to use multiple annotation libraries is for 
every annotation library to expose an overloades 'interpret_annotation' 
function that the application can hook in order to handle new annotation types".

I think you're right that overloaded functions are a possible solution to this 
problem, but that doesn't obviate the need for the PEP to address the question 
explicitly (and using overloaded functions for this strikes me as hitting a 
very small nail with a very large hammer).

With the function overloading solution, you would need to do three things in 
order to get two frameworks to cooperate:
   1. Define your own Annotation type and register it with the frameworks you 
are using
   2. Define a decorator to wrap the annotations in a function __signature__ 
into your custom annotation type
   3. Apply your decorator to functions before the decorators for the 
annotation libraries are invoked

Overloading a standard type (like tuple) wouldn't work, as you might have two 
different modules, both using the same annotation library, that want it to 
interpret tuples in two different ways (e.g. in module A, the library's info 
is at index 0, while in module B it is at index 1).

So, for example:

   @library_A_type_processor
   @library_B_docstring_processor
   @handle_annotations
   def func(a: (int, "an int"),
            b: (str, "a string"))
            -> (str, "returns a string, too!):
     # do something

   def handle_annotations(f):
      note_dict = f.__signature__.annotations
      for param, note in note_dict.items():
              note_dict[param] = MyAnnotation(note)
      return f

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__

Then our function declaration and decorator would look like:

   @handle_annotations
   def func(a: (int, "an int"), b: (str, "a string")) -> (str, "returns!):
     # do something


   def handle_annotations(f):
      decorators = library_A_type_processor, library_B_docstring_processor
      note_dicts = {}, {}
      for param, note in f.__signature__.annotations.iteritems():
          for note_dict, subnote in zip(note_dicts, note):
              note_dict[param] = subnote
      for decorator, note_dict in zip(decorators, note_dicts):
          f = decorator(f, note_dict)
      return f

Writing a factory function to handle chaining of an arbitrary number of 
annotation interpreting libraries would be trivial, with the set of decroators 
provided as positional arguments if your notes are in a tuple, and as a 
keyword arguments if the notes are in a dictionary.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-3000 mailing list