From 8a6f51bf268eaf99d587abb2985fb9b944f17444 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 28 Mar 2024 21:04:48 -0600 Subject: [PATCH] clarify interaction of Final and dataclass --- docs/spec/dataclasses.rst | 8 ++++++++ docs/spec/qualifiers.rst | 22 +++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index b2466fef1..ea2654e9d 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -516,6 +516,14 @@ This includes, but is not limited to, the following semantics: * ClassVar attributes are not considered dataclass fields and are `ignored by dataclass mechanisms `_. +* Dataclass fields annotated with ``Final`` and initialized in the class body + are not considered class attributes unless explicitly annotated with + ``ClassVar``. For example, ``x: Final[int] = 3`` in a dataclass body is a + dataclass field (and thus instance attribute) named ``x`` with a default + value of ``3`` in the generated ``__init__`` method, which cannot be + reassigned after ``__init__``. A final class variable on a dataclass should + be explicitly annotated as ``x: ClassVar[Final[int]] = 3``. + Undefined behavior ^^^^^^^^^^^^^^^^^^ diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst index 07b3a3e74..cf727fa6c 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -147,19 +147,27 @@ 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:: +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