[Python-ideas] Optional static typing -- the crossroads

Terry Reedy tjreedy at udel.edu
Fri Aug 15 23:18:11 CEST 2014


On 8/15/2014 5:48 AM, Nick Coghlan wrote:
> On 15 August 2014 19:38, Terry Reedy <tjreedy at udel.edu> wrote:
>> On 8/15/2014 12:40 AM, Nick Coghlan wrote:
>>>
>>> On 15 August 2014 09:56, Guido van Rossum <guido at python.org> wrote:
>>>>
>>>>
>>>> I don't buy the argument that PEP 3107 promises that annotations are
>>>> completely free of inherent semantics.
>>>
>>>
>>> It's also worth noting the corresponding bullet point in PEP 3100
>>> (under http://www.python.org/dev/peps/pep-3100/#core-language):
>>>
>>> * Add optional declarations for static typing [45] [10] [done]
>>
>> ...
>>
>>> Linters/checkers may also want to provide a configurable way to say
>>> "the presence of decorator <X> means the annotations on that function
>>> aren't type markers". That ties in with the recommendation we added to
>>> PEP 8 a while back: "It is recommended that third party experiments
>>> with annotations use an associated decorator to indicate how the
>>> annotation should be interpreted."

I claim that the mere presence of a decorator in the *source* is not 
enough. The decorator for non-type markers should do something with 
.__annotations__ -- in particular, remove the non-type markers.  The 
presence of a decorator in the source does not necessarily leave a trace 
on the returned function object for runtime detection.

>> Depending on the checker, this suggests that non-type-check annotations need
>> not be deprecated. If a decorator wraps a function with an unannotated
>> wrapper, then the checker should see the result as unannotated, rather than
>> looking for a wrapped attribute. Also, a decorator can remove non-type
>> annotations and act on them, store them in a closure variable, or store them
>> on the function in a different name.

"Depending on the checker" alludes to the fact that there are two types 
of annotation consumers: those that read the source or parsed source 
(ast) and the annotations therein and those that look at the function 
.__annotations__ attribute after compilation.  Inspect.signature is an 
example of the latter. (If there were none, there would be no purpose to 
.__annotations__!)

Suppose an integer square root function were annotated with with type 
and non-type information.

 >>> def nsqrt(n:(int, 'random non-type info'))->(int, 'more non-type 
info'): pass

To me, inspect.signature already assumes that annotations are about 
type, which means that this horse has already left barn. In 3.4.1:

 >>> from inspect import signature as sig
 >>> str(sig(nsqrt))
"(n:(<class 'int'>, 'random non-type info')) -> (<class 'int'>, 'more 
non-type info')"

Typing 'nsqrt(' in Idle and pausing a fraction of a second brings up a 
calltip with the same string (without the outer quotes). To me, having 
random non-type info in the signature and calltip is noise and therefore 
wrong. So I agree that the non-standard annotation should be signaled by 
a decorator *and* suggest that the decorator should remove the 
non-standard annotation, which is easily done, so that the signature 
string for the above would be

"(n:<class 'int') -> <class 'int'>"

I summarized the above, perhaps not the best I could have, with

"Given these possibilities, all that is needs be said is "After a 
function is post-processed by decorators, any remaining annotations 
should be for type-checking or documentation."

> No, many (most?)

The future relative proportion of pre- and post-compile annotation 
consumers is not relevant to my argument. The stdlib already has a 
important post-compile consumer that is already somewhat broken by 
non-type info remaining in .__annotations__.

 > linters and IDEs will run off the AST without
> actually executing the code, so they'll see the annotations, even if
> they get stripped by the decorator at runtime.

Being aware of this, I concluded the post with the following that 
already said this.

"For checkers that do look at the source, or the AST before compiling,"

*and* I went on to suggest a solution.

"the rule could be to ignore string annotations. Decorators can always 
eval, or perhaps safe_eval, strings."

In other words, if type annotations were to be classes, as proposed, 
then non-type annotations should not be classes, so that pre-compile 
annotation consumers could easily ignore them. In particular, I 
suggested literal strings, which are easily recognized in source, as 
well as in asts.


To put this all another way --

The new-in-3.0 annotation feature has two components: a python function 
source syntax, and a function compile behavior of adding a dict 
attribute as .__annotations__.  If I understand correctly, Argument 
Clinic piggybacks on this by adding a mechanism to produce 
.__annotations__ from C source -- mainly for use by .signature, but also 
by another other .__annotations__ users. The two components -- source 
annotations and .__annotations__ dict -- each have their consumers.

Currently, annotation values are untyped (or AnyTyped).  Guido has 
proposed favoring type annotations. I support that and suggest that such 
favoritism is already needed for the annotation dict. So I would 
strengthen the PEP 8 recommendation.

Guido has also suggested 'deprecating' non-type annotations.  That would 
literally mean raising an error either when source is compiled or when 
def statements are executed.  I think deprecation in this sense is both 
unwise, since non-type annotation were explicitly invited, and 
unnecessary for the purpose of favoring type annotations. The point of 
my previous post was to explore what restrictions *are* necessary.  In 
summary, I suggest 1. use distinct syntax (this depends on what is 
adopted for type annotations); 2. decorate (as already suggested in PEP 
8); 3. clean .__annotations__ (which should also be suggested in PEP 8).

-- 
Terry Jan Reedy



More information about the Python-ideas mailing list