Clarify `Final` assignment

from Final semantics:

  • There must be exactly one assignment to a final name.

this leads to the following contradiction:
Code sample in basedpyright playground

from typing import Final

def random_coin_flip() -> bool:
    return True  # pre-computed using a real coin

a: Final = 1 if random_coin_flip() else 0  # valid, a single assignment

b: Final[int]  # the type could potentially be inferred here, but lets not say that it is mandatory to support

if random_coin_flip():
    b = 1
else:
    b = 0  # invalid, a secondary assignment

we can see that the semantics of a and b are identical, yet we observe different type checking behaviour

i don’t think that b should have this error

5 Likes

Do we even need to support module-scope Final without an initializer?

from typing import Final

A: Final
A = 1

Currently only pyright and ty allow this; mypy, zuban, pyrefly, and pycroscope reject it saying that a Final variable needs an initializer.

I know the spec explicitly allows Final instance attributes to be declared in class scope without an initializer, but that seems different and more useful.

1 Like

It’s useful when importing backports or optional accelerators to have constants defined based on which actual backend for something is in use.

I’m in favor of the change, but I’m not sure the wording is as precise as it could be.

This is not the only place with typing where taking into account actual code flow would improve specified behavior. I think the proposed wording and specification change is fine for now, but there’s significantly more that could be improved in the same vein which might later result in wanting more precise language.

1 Like

The same branchy-initialization case that the spec change aims to allow, effectively requires an initializer-less Final declaration; otherwise it would run afoul of ā€œonly one Final declaration allowedā€.

It would indeed be very useful, as quite often I do things like:

if random_coin_flip():
    b = 1
    # Some extra stuff
else:
    b = 0
    # Some other extra stuff

But using Final in an assignment-less declaration isn’t to my taste, personally.