From 7a70cd6313764e7b6c2c5b4ed439189e5c3e85af Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 11 Apr 2024 06:51:01 -0600 Subject: [PATCH] spec: clarify interaction of Final and dataclass (#1669) --- docs/spec/dataclasses.rst | 10 ++++++++++ docs/spec/qualifiers.rst | 26 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index feeabdead..d3229a5d5 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -516,6 +516,16 @@ 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 + 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, + ``x: Final[int] = 3`` is a dataclass field named ``x`` with a default value + of ``3`` in the generated ``__init__`` method. A final class variable on a + dataclass must be explicitly annotated as e.g. ``x: ClassVar[Final[int]] = + 3``. + Undefined behavior ^^^^^^^^^^^^^^^^^^ diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index 07b3a3e74..223e9ed1c 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -147,19 +147,31 @@ be initialized in the ``__init__`` method (except in stub files):: def __init__(self) -> None: self.x = 1 # Good -Type checkers should infer a final attribute that is initialized in -a class body as being a class variable. Variables should not be annotated -with both ``ClassVar`` and ``Final``. - -``Final`` may only be used as the outermost type 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:: +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``. + +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 +``x: Final[int] = 3`` creates a dataclass field and instance-level final +attribute ``x`` with default value ``3``; ``x: ClassVar[Final[int]] = 3`` is +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`` 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:: x: list[Final[int]] = [] # Error! def fun(x: Final[List[int]]) -> None: # Error! ... +``Final`` may be wrapped only by other type qualifiers (e.g. ``ClassVar`` or +``Annotation``). It cannot be used in a type parameter (e.g. +``list[Final[int]]`` is not permitted.) + Note that declaring a name as final only guarantees that the name will not be re-bound to another value, but does not make the value immutable. Immutable ABCs and containers may be used in combination