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. 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. It's an annotation because that's the specific *purpose* of the expression in that context. 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. 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.
"Expression" is something that you need its value right now, and "annotation" is something that, well, annotates the code you see right now.
Right. In the case of Python, function annotations **do** have a runtime effect: the expressions are evaluated, and the evaluated results are assigned in function.__annotations__ and made available for runtime introspection. Don't think that function annotations are **only** for the static type checker. Python is a much richer language than that!
It is also easy to forget, and the result might be a (very uninteresting) exception in certain untested paths, e.g. inside functions.
Unlikely, unless you're talking about functions nested inside other functions, or unusual (but legal and sometimes useful) conditional definitions:
I was thinking about the former, but yeah, uncovered code will fail at runtime, possibly in production, for *no* real reason. I do not claim that this is common, but it is definitely unnecessary - unlike initialization expressions.
Unnecessary? class MyClass: pass def function(arg: MyCalss): ... I want to see an immediate NameError here, thank you very much, even if I'm not running a static checker. I don't want to have to manually call: function.__annotations__['arg']() to see whether or not the annotation is valid. I accept that using strings as forward annotations is not a foolproof solution either: def function(arg: 'MyCalss'): ... but let's not jump into a "fix" that actually makes things worse.
if condition: # forward reference to MyClass def f(arg:'MyClass'): ... else: # oops, untested path def f(arg:MyClass): ...
class MyClass: ...
But generally speaking, that sort of code is unusual, and besides, if you're doing this, either the static type checker won't be able to cope with it at all (in which case there's little point in annotating the function), or it will cope, and detect the invalid annotation.
Why would it detect invalid annotation here? It shouldn't.
MyClass doesn't exist at that point, so it is in invalid annotation.
help editors in syntax highlighting and name lookup.
But will harm runtime introspection.
Very little. And to quote Frank Miller, “An old man dies, a little girl lives. Fair trade.”
Not to the old man, and especially not if the little girl is a psychopath who grows up to become a mass murdering totalitarian dictator. -- Steve