Missing behavior around bare annotations

PEP-526, which introduced variable annotations, contains this clause

Duplicate type annotations will be ignored. However, static type checkers may issue a warning for annotations of the same variable by a different type:

a: int
a: str  # Static type checker may or may not warn about this.

This reads straightforward to me, only the first annotation of a variable is stored in __annotations__ and subsequent annotations have no effect. But that is not what happens

>>> foo:str
>>> __annotations__
... {'foo': <class 'str'>}
>>> foo:int
>>> __annotations__
... {'foo': <class 'int'>}

Perhaps the will be is directed at external type checking tools, but no where else does the PEP enforce behavior on tooling; it is explicitly a non-goal to enforce behavior.

I could not find any reference to using multiple annotations on the same variable in the python docs (and nothing about ignoring annotations). So, as PEPs are largely historical documents, maybe its lack of mention in official docs is blessing the current behavior. But it seems like an explicit case called out in the PEP and not translated to CPython.

Note I only came across this because I was surprised that using multiple bare annotations on the same variable in the same scope was allowed at all by Python. All type checkers I used follow the PEP’s advice and warn about subsequent annotations, and continue to use the first type annotation for the rest of the code.

This goes well beyond my pay grade (and most of the folks you’ll find on users); if you don’t get a response here, this might be the sort of question you could consider asking on the Core Dev forum section.

I agree that the wording is a little unclear. For starters, they’re not
really duplicates if they are different:

so we have to understand it to mean duplicate identifiers.

Your interpretation that “only the first annotation” is stored does not
follow from the literal wording given: “Duplicate type annotations will
be ignored.” If they (note plural) are ignored, we should expect that
“a” won’t show up in the annotation dict at all.

But more realistically, we should interpret “ignored” in the sense that
no error will be raised, not that it is dead code.

What seems to be happening is that the second and any subsequent
annotations overwrite the first. That matches the usual behaviour of
duplicate keys in dicts:

>>> d = {'a': int}
>>> d.update(a=str)
>>> d
{'a': <class 'str'>}

>>> d = {'a': int} | {'a': str}
>>> d
{'a': <class 'str'>}

>>> d = dict({'a': int}, a=str)
>>> d
{'a': <class 'str'>}

and remember that __annotations__ is a dict. In other words, last
annotation seen wins.

Fundamentally, duplicate annotations are no more inherently an error
than duplicate assignments. If you’re okay with:

a = 1
a = 'hello'

then you ought to be okay with:

I’m not convinced that “ignored” means “raises no error”. If python’s dict has this specific behavior called out in the official language spec

duplicate key assignments will be ignored

What would you expect the following code to result in?

spam["a"] = 42
spam["a"] = "ham"
spam["a"]

a) 42

b) “ham”

c) KeyError

I agree that the current annotations behavior follows dict assignment as, at least here, this is basically syntactic sugar;
x: str is the same as locals().__annotations__["x"] = str.

But just because annotations could be replicated by using an actual dict doesn’t mean it has to be have the same results as if each annotation was literally replaced with a key assignment.

@steven.daprano your ability to intuit the python hive mind does seem to be spot on. I went through the original commit history of the PEP and this example started out as two subsequent, duplicate, a: int statements. It was changed from this to explicitly call out the warning that type checkers should raise. This would seem to close my original confusion.