[Python-ideas] Delay evaluation of annotations
Steven D'Aprano
steve at pearwood.info
Fri Sep 23 08:10:12 EDT 2016
On Fri, Sep 23, 2016 at 10:17:15AM +0000, אלעזר wrote:
> On Fri, Sep 23, 2016 at 6:06 AM Steven D'Aprano <steve at pearwood.info> wrote:
> > On Thu, Sep 22, 2016 at 07:21:18PM +0000, אלעזר wrote:
> > > On Thu, Sep 22, 2016 at 9:43 PM Steven D'Aprano wrote:
> > > > On Thu, Sep 22, 2016 at 05:19:12PM +0000, אלעזר wrote:
> > > > > Hi all,
> > > > >
> > > > > Annotations of function parameters and variables are evaluated when
> > > > > encountered.
> > > >
> > > > Right, like all other Python expressions in general, and specifically
> > > > like function parameter default arguments.
> > > >
> > >
> > > Just because you call it "expression", when for most purposes it isn't -
> > > it is an annotation.
> >
> > It is *both*. It's an expression, because it's not a statement or a
> > block.
>
>
> Did you just use a false-trichotomy argument? :)
No.
You are the one trying to deny that annotations are expressions -- I'm
saying that they are both annotations and expressions at the same time.
There's no dichotomy here, since the two are not mutually exclusive.
(The word here is dichotomy, not trichotomy, since there's only two
things under discussion, not three.)
> > You cannot write:
> >
> > def func(arg1: while flag: sleep(1), arg2: raise ValueError):
> > ...
>
> > because the annotation must be a legal Python expression, not a code
> > block or a statement.
>
>
> This is the situation I'm asking to change
That's a much bigger change than what you suggested earlier, changing
function annotations to lazy evaluation instead of eager.
Supporting non-expressions as annotations -- what's your use-case? Under
what circumstances would you want to annotate an function parameter with
a code block instead of an expression?
> > It's an annotation because that's the
> > specific *purpose* of the expression in that context.
>
> Exactly! Ergo, this is an annotation.
I've never denied that annotations are annotations, or that annotations
are used to annotate function parameters. I'm not sure why you are
giving a triumphant cry of "Exactly!" here -- it's not under dispute
that annotations are annotations.
And it shouldn't be under dispute that annotations are expressions.
They're not code blocks. They're not statements. What else could they be
apart from expressions?
The PEP that introduced them describes them as expressions:
Function annotations are nothing more than a way of associating
arbitrary Python EXPRESSIONS with various parts of a function at
compile-time. [Emphasis added.]
https://www.python.org/dev/peps/pep-3107/
and they are documented as an expression:
parameter ::= identifier [":" expression]
Parameters may have annotations of the form “: expression” following
the parameter name. ... These annotations can be any valid Python
expression
https://docs.python.org/3/reference/compound_stmts.html#function-definitions
I think its time to give up arguing that annotations aren't expressions.
> > As an analogy: would you argue that it is wrong to call the for-loop
> > iterable an expression?
> >
> > for <target-list> in <expression>:
> > block
> >
> > I trust that you understand that the loop iterable can be any expression
> > that evaluates to an iterable. Well, annotations can be any expression
> > that evaluates to anything at all, but for the purposes of type
> > checking, are expected to evaluate to a string or a type object.
> >
> >
> for-loop iterable is an expression, evaluated at runtime, _for_ the
> resulting value to be used in computation. A perfectly standard expression.
> Nothing fancy.
Right. And so are annotations.
You want to make them fancy, give them super-powers, in order to solve
the forward reference problem. I don't think that the problem is serious
enough to justify changing the semantics of annotation evaluation and
make them non-standard, fancy, lazy-evaluated expressions.
> > In the case of function annotations, remember that they can be any
> > legal Python expression. They're not even guaranteed to be type
> > annotations. Guido has expressed a strong preference that they are only
> > used as type annotations, but he hasn't yet banned other uses (and I
> > hope he doesn't), so any "solution" for a type annotation problem must
> > not break other uses.
> >
> >
> Must *allow* other use cases. My proposal allows: just evaluate them at the
> time of their use, instead at definition time.
I meant what I said. Changing the evaluation model for annotations is a
big semantic change, a backwards-incompatible change. It's not just
adding new syntax for something that was a syntax error before, it would
be changing the meaning of existing Python code.
The transition from 3.6 to 3.7 is not like that from 2.x to 3.0 --
backwards compatibility is a hard requirement. Code that works a certain
way in 3.6 is expected to work the same way in 3.7 onwards, unless we go
through a deprecation period of at least one full release, and probably
with a `from __future__ import ...` directive required. There may be a
little bit of wiggle-room available for small changes in behaviour,
under some circumstances -- but changing the evaluation model is
unlikely to be judged to be a "small" change.
In any case, before such a backwards-incompatible change would be
allowed, you would have to prove that it was needed.
[...]
> > class MyClass:
> > pass
> >
> > def function(arg: MyCalss):
> > ...
> >
> > I want to see an immediate NameError here, thank you very much
>
> Two things to note here:
> A. IDEs will point at this NameError
Some or them might. Not everyone uses an IDE, it is not a requirement
for Python programmers. Runtime exceptions are still, and always will
be, the primary way of detecting such errors.
> B. Type checkers catch this NameError
Likewise for type checkers.
> C. Even the compiler can be made to catch this name error, since the name
> MyCalss is bound to builtins where it does not exist
How do you know it doesn't exist? Any module, any function, any class,
any attribute access, might have added something called MyCalss to this
module's namespace, or to the built-ins.
It's okay for a non-compulsory type-checker, linter or editor to make
common-sense assumptions about built-ins. But the compiler cannot: it
has no way of knowing *for sure* whether or not MyCalss exists until
runtime. It has to actually do the name lookup, and see what happens.
> - you see, name lookup does happen at compile time anyway.
It really doesn't.
You might be confusing function-definition time (which occurs at
runtime) with compile time. When the function is defined, which occurs
at runtime, the name MyCalss must exist or a NameError will occur. But
that's not at compile time.
> D. Really, where's the error here? if no tool looks at this signature,
> there's nothing wrong with it - As a human I understand perfectly.
class CircuitDC:
...
class CircuitAC:
...
def func(arg: CircuitSC):
...
Do you still understand perfectly what I mean?
--
Steve
More information about the Python-ideas
mailing list