From b1c4442601ac7144596915d848fdcef42960096f Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 20 Oct 2026 16:48:48 +0200 Subject: [PATCH 1/4] Introduce the "bare"/"explictly typed" Final terminology Closes: #2356 --- docs/guides/libraries.rst | 4 ++-- docs/spec/dataclasses.rst | 5 +++-- docs/spec/distributing.rst | 4 ++-- docs/spec/glossary.rst | 8 ++++++++ docs/spec/literal.rst | 4 ++-- docs/spec/qualifiers.rst | 34 +++++++++++++++++++--------------- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/docs/guides/libraries.rst b/docs/guides/libraries.rst index 8e62af9c5..66404aac8 100644 --- a/docs/guides/libraries.rst +++ b/docs/guides/libraries.rst @@ -568,10 +568,10 @@ type annotation would be redundant. COLOR_FORMAT_RGB: Literal["rgb"] = "rgb" LATEST_VERSION: tuple[int, int] = (4, 5) - # Final variable with inferred type + # Bare Final variable with inferred type ColorFormatRgb: Final = "rgb" - # Final variable with explicit type + # Explicitly typed Final variable ColorFormatRgb: Final[Literal["rgb"]] = "rgb" LATEST_VERSION: Final[tuple[int, int]] = (4, 5) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index ef08344b4..0dee95631 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -526,8 +526,9 @@ This includes, but is not limited to, the following semantics: * ClassVar attributes are not considered dataclass fields and are `ignored by dataclass mechanisms `_. -* A dataclass field may be annotated with ``Final[...]``. For example, ``x: - Final[int]`` in a dataclass body specifies a dataclass field ``x``, which +* A dataclass field may be annotated with an :term:`explicitly typed Final`. + For example, ``x: Final[int]`` + in a dataclass body specifies a dataclass field ``x``, which will be initialized in the generated ``__init__`` and cannot be assigned to thereafter. A ``Final`` dataclass field initialized in the class body is not a class attribute unless explicitly annotated with ``ClassVar``. For example, diff --git a/docs/spec/distributing.rst b/docs/spec/distributing.rst index 13cfff81a..c40f89f3e 100644 --- a/docs/spec/distributing.rst +++ b/docs/spec/distributing.rst @@ -105,8 +105,8 @@ assignments:: x = 0 # type: int x = ... # type: int -The :ref:`Literal shortcut using Final ` should be -supported:: +The :ref:`Literal shortcut using bare Final ` +should be supported:: x: Final = 0 # type is Literal[0] diff --git a/docs/spec/glossary.rst b/docs/spec/glossary.rst index 340dcbd4a..093f83f12 100644 --- a/docs/spec/glossary.rst +++ b/docs/spec/glossary.rst @@ -27,6 +27,10 @@ This section defines a few terms that may be used elsewhere in the specification ``B``, respectively, such that ``B'`` is a subtype of ``A'``. See :ref:`type-system-concepts`. + bare ``Final`` + A :ref:`Final ` annotation without an explicit type, + e.g. ``x: Final``. + closed A :ref:`TypedDict ` type is closed if it may not contain any additional :term:`items ` beyond those specified in the TypedDict definition. @@ -59,6 +63,10 @@ This section defines a few terms that may be used elsewhere in the specification also materializations of ``B``, and all materializations of ``B`` are also materializations of ``A``. + explicitly typed ``Final`` + A :ref:`Final ` annotation used as a :term:`type qualifier`, + i.e. with an explicit type: ``x: Final[int]``. + extra items A :ref:`TypedDict ` type with extra items may contain arbitrary additional key-value pairs beyond those specified in the TypedDict definition, but those diff --git a/docs/spec/literal.rst b/docs/spec/literal.rst index 0eb93c27a..09674e952 100644 --- a/docs/spec/literal.rst +++ b/docs/spec/literal.rst @@ -504,7 +504,7 @@ involving Literal bools. For example, we can combine ``Literal[True]``, Interactions with Final """"""""""""""""""""""" -The ``Final`` qualifier can be used to declare that some variable or +A :term:`bare Final` can be used to declare that some variable or attribute cannot be reassigned:: foo: Final = 3 @@ -530,7 +530,7 @@ any context that expects a ``Literal[value]``. Type checkers are not obligated to understand any other uses of Final. For example, whether or not the following program type checks is left unspecified:: - # Note: The assignment does not exactly match the form 'var: Final = value'. + # Note: explicitly typed Final qualifiers have unspecified behavior. bar1: Final[int] = 3 expects_three(bar1) # May or may not be accepted by type checkers diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index 6814692e0..6ad698eea 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -81,13 +81,18 @@ variable or attribute should not be reassigned, redefined, or overridden. Syntax ^^^^^^ -``Final`` may be used in one of several forms: +``Final`` may be used in one of two forms: -* With an explicit type, using the syntax ``Final[]``. Example:: +* As :term:`explicitly typed Final`, using the syntax ``Final[]``. + Example:: ID: Final[float] = 1 -* With no type annotation. Example:: + In class bodies and stub files the right hand side can be omitted:: + + ID: Final[float] + +* As :term:`bare Final`, with no type annotation. Example:: ID: Final = 1 @@ -95,16 +100,6 @@ Syntax determine the type of ``ID`` (here, likely, ``int``). Note that unlike for generic classes this is *not* the same as ``Final[Any]``. -* In class bodies and stub files you can omit the right hand side and just write - ``ID: Final[float]``. If the right hand side is omitted, there must - be an explicit type argument to ``Final``. - -* Finally, as ``self.id: Final = 1`` (also optionally with a type in - square brackets). This is allowed *only* in ``__init__`` methods, so - that the final instance attribute is assigned only once when an - instance is created. - - Semantics and examples ^^^^^^^^^^^^^^^^^^^^^^ @@ -155,9 +150,18 @@ be initialized in the ``__init__`` method (except in stub files):: def __init__(self) -> None: self.x = 1 # Good +Alternatively, ``Final`` attributes can be assigned in and *only* in +``__init__`` methods, so that the final instance attribute is assigned only +once when an instance is created:: + + class ImmutableAttributes: + def __init__(self) -> None: + self.x: Final = 3 + self.y: Final[str] = "knight" + The generated ``__init__`` method of :doc:`dataclasses` qualifies for this -requirement: a bare ``x: Final[int]`` is permitted in a dataclass body, because -the generated ``__init__`` will initialize ``x``. +requirement: a ``x: Final[int]`` without right side is permitted in a dataclass +body, because the generated ``__init__`` will initialize ``x``. Type checkers should infer a final attribute that is initialized in a class body as being a class variable, except in the case of :doc:`dataclasses`, where From bddb733a2e32528dd4c79b16d6f812cd87498223 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 20 Oct 2026 17:20:37 +0200 Subject: [PATCH 2/4] Improvements --- docs/guides/writing_stubs.rst | 4 ++-- docs/spec/dataclasses.rst | 4 ++-- docs/spec/glossary.rst | 8 ++++---- docs/spec/literal.rst | 10 +++++----- docs/spec/qualifiers.rst | 34 ++++++++++++++++++---------------- docs/spec/typeddict.rst | 4 ++-- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/docs/guides/writing_stubs.rst b/docs/guides/writing_stubs.rst index 7964566c9..af0b402cd 100644 --- a/docs/guides/writing_stubs.rst +++ b/docs/guides/writing_stubs.rst @@ -349,8 +349,8 @@ still recommended to separately define known attributes. Constants --------- -When the value of a constant is important, mark it as ``Final`` and assign it -to its value. +When the value of a constant is important, mark it as a :term:`bare Final` and +assign it to its value. Yes:: diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index 0dee95631..3d9648923 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -526,8 +526,8 @@ This includes, but is not limited to, the following semantics: * ClassVar attributes are not considered dataclass fields and are `ignored by dataclass mechanisms `_. -* A dataclass field may be annotated with an :term:`explicitly typed Final`. - For example, ``x: Final[int]`` +* A dataclass field may be annotated with an :term:`explicitly typed Final` + :term:`type qualifier`. For example, ``x: Final[int]`` in a dataclass body specifies a dataclass field ``x``, which will be initialized in the generated ``__init__`` and cannot be assigned to thereafter. A ``Final`` dataclass field initialized in the class body is not diff --git a/docs/spec/glossary.rst b/docs/spec/glossary.rst index 093f83f12..3c8a32cfd 100644 --- a/docs/spec/glossary.rst +++ b/docs/spec/glossary.rst @@ -28,8 +28,8 @@ This section defines a few terms that may be used elsewhere in the specification :ref:`type-system-concepts`. bare ``Final`` - A :ref:`Final ` annotation without an explicit type, - e.g. ``x: Final``. + A :ref:`Final ` :term:`type qualifier` without an + explicit type, e.g. ``x: Final``. closed A :ref:`TypedDict ` type is closed if it may not contain any @@ -64,8 +64,8 @@ This section defines a few terms that may be used elsewhere in the specification also materializations of ``A``. explicitly typed ``Final`` - A :ref:`Final ` annotation used as a :term:`type qualifier`, - i.e. with an explicit type: ``x: Final[int]``. + A :ref:`Final ` :term:`type qualifier` with an explicit + type, e.g. ``x: Final[int]``. extra items A :ref:`TypedDict ` type with extra items may contain arbitrary diff --git a/docs/spec/literal.rst b/docs/spec/literal.rst index 09674e952..7254632b2 100644 --- a/docs/spec/literal.rst +++ b/docs/spec/literal.rst @@ -504,8 +504,8 @@ involving Literal bools. For example, we can combine ``Literal[True]``, Interactions with Final """"""""""""""""""""""" -A :term:`bare Final` can be used to declare that some variable or -attribute cannot be reassigned:: +A :term:`bare Final` type qualifier can be used to declare that some variable +or attribute cannot be reassigned:: foo: Final = 3 foo = 4 # Error: 'foo' is declared to be Final @@ -518,8 +518,8 @@ is valid to use in any context that expects a ``Literal[3]``:: expects_three(foo) # Type checks, since 'foo' is Final and equal to 3 -The ``Final`` qualifier serves as a shorthand for declaring that a variable -is *effectively Literal*. +A :term:`bare Final` type qualifier serves as a shorthand for declaring that a +variable is *effectively Literal*. Type checkers are expected to support this shortcut. Specifically, given a variable or attribute assignment @@ -530,7 +530,7 @@ any context that expects a ``Literal[value]``. Type checkers are not obligated to understand any other uses of Final. For example, whether or not the following program type checks is left unspecified:: - # Note: explicitly typed Final qualifiers have unspecified behavior. + # Note: Literal inference is unspecified for explicitly typed Final. bar1: Final[int] = 3 expects_three(bar1) # May or may not be accepted by type checkers diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index 6ad698eea..f24a5713e 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -83,7 +83,7 @@ Syntax ``Final`` may be used in one of two forms: -* As :term:`explicitly typed Final`, using the syntax ``Final[]``. +* As an :term:`explicitly typed Final`, using the syntax ``Final[]``. Example:: ID: Final[float] = 1 @@ -92,10 +92,12 @@ Syntax ID: Final[float] -* As :term:`bare Final`, with no type annotation. Example:: +* As a :term:`bare Final`, without a type argument. Example:: ID: Final = 1 + Bare ``Final`` annotations must have an assigned value. + The typechecker should apply its usual type inference mechanisms to determine the type of ``ID`` (here, likely, ``int``). Note that unlike for generic classes this is *not* the same as ``Final[Any]``. @@ -150,18 +152,9 @@ be initialized in the ``__init__`` method (except in stub files):: def __init__(self) -> None: self.x = 1 # Good -Alternatively, ``Final`` attributes can be assigned in and *only* in -``__init__`` methods, so that the final instance attribute is assigned only -once when an instance is created:: - - class ImmutableAttributes: - def __init__(self) -> None: - self.x: Final = 3 - self.y: Final[str] = "knight" - The generated ``__init__`` method of :doc:`dataclasses` qualifies for this -requirement: a ``x: Final[int]`` without right side is permitted in a dataclass -body, because the generated ``__init__`` will initialize ``x``. +requirement: an ``x: Final[int]`` without an initializer is permitted in a +dataclass body, because the generated ``__init__`` will initialize ``x``. Type checkers should infer a final attribute that is initialized in a class body as being a class variable, except in the case of :doc:`dataclasses`, where @@ -171,6 +164,15 @@ necessary to create a final class variable with value ``3``. In non-dataclasses, combining ``ClassVar`` and ``Final`` is redundant, and type checkers may choose to warn or error on the redundancy. +``Final`` attributes can also be declared in and *only* in ``__init__`` +methods, so that the final instance attribute is assigned only once when an +instance is created:: + + class ImmutableAttributes: + def __init__(self) -> None: + self.x: Final = 3 + self.y: Final[str] = "knight" + ``Final`` may only be used in assignments or variable annotations. Using it in any other position is an error. In particular, ``Final`` can't be used in annotations for function arguments:: @@ -197,9 +199,9 @@ with ``Final`` to prevent mutating such values:: z: Final = ('a', 'b') # Also works -Type checkers should treat uses of a final name that was initialized -with a literal as if it was replaced by the literal. For example, the -following should be allowed:: +Type checkers should treat uses of name declared with a :term:`bare Final` +that was initialized with a literal as if it was replaced by the literal. For +example, the following should be allowed:: from typing import NamedTuple, Final diff --git a/docs/spec/typeddict.rst b/docs/spec/typeddict.rst index cff7e8499..81de917a6 100644 --- a/docs/spec/typeddict.rst +++ b/docs/spec/typeddict.rst @@ -732,8 +732,8 @@ certain items. For example, ``d['x'] = 1`` should generate a type check error if ``'x'`` is not a valid key for ``d`` (which is assumed to be a TypedDict type), unless ``d`` has mutable :term:`extra items` of a compatible type. -Type checkers should allow :ref:`final names ` with -string values to be used instead of string literals in operations on +Type checkers should allow names declared with a :term:`bare Final` and a +string value to be used instead of string literals in operations on TypedDict objects. For example, this is valid:: YEAR: Final = 'year' From 0ec8d8ea2097d98dd20f6937ae0ada52194fc3dd Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 20 Oct 2026 23:25:13 +0200 Subject: [PATCH 3/4] Apply batched suggestions from code review Co-authored-by: Joren Hammudoglu --- docs/spec/distributing.rst | 2 +- docs/spec/qualifiers.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/distributing.rst b/docs/spec/distributing.rst index c40f89f3e..93edf5666 100644 --- a/docs/spec/distributing.rst +++ b/docs/spec/distributing.rst @@ -105,7 +105,7 @@ assignments:: x = 0 # type: int x = ... # type: int -The :ref:`Literal shortcut using bare Final ` +The :ref:`Literal shortcut using a bare Final ` should be supported:: x: Final = 0 # type is Literal[0] diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index f24a5713e..470fb4598 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -199,7 +199,7 @@ with ``Final`` to prevent mutating such values:: z: Final = ('a', 'b') # Also works -Type checkers should treat uses of name declared with a :term:`bare Final` +Type checkers should treat uses of names declared with a :term:`bare Final` that was initialized with a literal as if it was replaced by the literal. For example, the following should be allowed:: From f652160ccc8f881a6cefd94bced445e1592b2762 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 20 Oct 2026 23:27:40 +0200 Subject: [PATCH 4/4] Apply suggestion from code review --- docs/spec/qualifiers.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index 470fb4598..b05202125 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -164,9 +164,9 @@ necessary to create a final class variable with value ``3``. In non-dataclasses, combining ``ClassVar`` and ``Final`` is redundant, and type checkers may choose to warn or error on the redundancy. -``Final`` attributes can also be declared in and *only* in ``__init__`` -methods, so that the final instance attribute is assigned only once when an -instance is created:: +``Final`` attributes can also be declared in ``__init__`` methods, as +annotations on an assignment to an attribute on ``self``. This is allowed +*only* in ``__init__``, not in any other method:: class ImmutableAttributes: def __init__(self) -> None: