21

In Python 3.6, the new Variable Annotations were introduced in the language.

But, when a type does not exist, the two different things can happen:

>>> def test():
...     a: something = 0
... 
>>> test()
>>> 
>>> a: something = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'something' is not defined

Why is the non-existing type handling behavior different? Would not it potentially cause one to overlook the undefined types in the functions?


Notes

Tried with both Python 3.6 RC1 and RC2 - same behavior.

PyCharm highlights something as "unresolved reference" in both inside and outside the function.

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195

4 Answers4

14

The behaviour of the local variable (i.e. inside the function) is at least documented in the section Runtime Effects of Type Annotations:

Annotating a local variable will cause the interpreter to treat it as a local, even if it was never assigned to. Annotations for local variables will not be evaluated:

def f():
    x: NonexistentName  # No error.

And goes on to explain the difference for global variables:

However, if it is at a module or class level, then the type will be evaluated:

x: NonexistentName  # Error!
class X:
    var: NonexistentName  # Error!

The behaviour seems surprising to me, so I can only offer my guess as to the reasoning: if we put the code in a module, then Python wants to store the annotations.

# typething.py
def test():
    a: something = 0

test()


something = ...

a: something = 0

Then import it:

>>> import typething
>>> typething.__annotations__
{'a': Ellipsis}
>>> typething.test.__annotations__
{}

Why it's necessary to store it on the module object, but not on the function object - I don't have a good answer yet. Perhaps it is for performance reasons, since the annotations are made by static code analysis and those names might change dynamically:

...the value of having annotations available locally does not offset the cost of having to create and populate the annotations dictionary on every function call. Therefore annotations at function level are not evaluated and not stored.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    Maybe they want to avoid problems with defining a local class `C` inside a function and then annotating variables in the function with `C`. `C` doesn't exist until its definition is executed, so you can't store it in an `__annotations__` attribute at function definition time. – user2357112 Dec 22 '16 at 22:08
  • Gotcha! The performance reasoning makes sense, I wonder if this would cause problems when you mistype a variable type in a function..I guess smart editors and tests should help with that. Thanks! – alecxe Dec 23 '16 at 17:56
7

The most direct answer for this (to complement @wim's answer) comes from the issue tracker on Github where the proposal was discussed:

[..] Finally, locals. Here I think we should not store the types -- the value of having the annotations available locally is just not enough to offset the cost of creating and populating the dictionary on each function call.

In fact, I don't even think that the type expression should be evaluated during the function execution. So for example:

def side_effect():
    print("Hello world")
def foo():
    a: side_effect()
    a = 12
    return a
foo()

should not print anything. (A type checker would also complain that side_effect() is not a valid type.)

From the BDFL himself :-) nor a dict created nor evaluation being performed.

Currently, function objects only store annotations as supplied in their definition:

def foo(a: int): 
    b: int = 0

get_type_hints(foo)   # from typing
{'a': <class 'int'>}

Creating another dictionary for the local variable annotations was apparently deemed too costly.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
4

You can go to https://www.python.org/ftp/python/3.6.0/ and download the RC2 version to test annotations but the released version as wim said is not yet released. I did however downloaded and tried your code using the Python3.6 interpreter and no errors showed up.

Dilmer
  • 169
  • 8
  • Ah, I have the Python 3.6.0rc1, let me check with Python 3.6.0rc2. Thanks! – alecxe Dec 22 '16 at 21:39
  • 2
    @Dilmer: No errors showed up? That is odd. I would expect the error to be raised in both cases. – BrenBarn Dec 22 '16 at 21:41
  • No errors "Python 3.6.0rc2 (v3.6.0rc2:800a67f7806d, Dec 16 2016, 14:12:21)" added the test function and call it as test() and clean result. – Dilmer Dec 22 '16 at 21:42
  • @Dilmer interesting. It behaves the same in rc2 as in rc1 for me: no error if inside a function and an error if outside. – alecxe Dec 22 '16 at 21:45
  • @Dilmer When you say "no errors showed up" can you confirm you checked for both the inside and outside function? – wim Dec 22 '16 at 21:55
  • @wim no errors inside or outside, I am running macOS Sierra – Dilmer Dec 22 '16 at 21:59
  • 1
    I tried on macOS Sierra, using Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13). It does raise the error, when outside of the function. – wim Dec 31 '16 at 17:17
-1

You can try write like this:

>>>a: 'something' = 0
ADR
  • 1,255
  • 9
  • 20