From 8a6f51bf268eaf99d587abb2985fb9b944f17444 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 28 Mar 2024 21:04:48 -0600 Subject: [PATCH 1/2] 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 From 0e3f3a7f4a2a22e5f69a3fb52f7a173711bf9f69 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 3 Apr 2024 15:42:35 -0600 Subject: [PATCH 2/2] Jelle comments --- docs/spec/dataclasses.rst | 16 +++++++++------- docs/spec/qualifiers.rst | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst index ea2654e9d..601ada405 100644 --- a/docs/spec/dataclasses.rst +++ b/docs/spec/dataclasses.rst @@ -516,13 +516,15 @@ 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``. +* 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 cf727fa6c..223e9ed1c 100644 --- a/docs/spec/qualifiers.rst +++ b/docs/spec/qualifiers.rst @@ -147,6 +147,10 @@ be initialized in the ``__init__`` method (except in stub files):: def __init__(self) -> None: self.x = 1 # Good +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