Thanks for clarifying the intention, especially with regards to this example:
class Item:
def __init__(self, x: int = 3) -> None:
self.x: Final[int] = x
This being said, I also think the re-use of Final as proposed here is too overloaded. The motivation to special-case Final in dataclasses isn’t too compelling on the basis of what it’s trying to prevent (instance.var = obj doesn’t work with type-checkers regardless of whether var: Final means var: ClassVar or the read-only instance variable proposal here).
IMO, implementing one of these (then deprecating and eventually removing the ability of Final to create dataclass fields) is a better solution:
- Allow
frozenper-field in a dataclass (I’d imagine this also has runtime implications): - New PEP allowing use of
typing.ReadOnly(introduced in PEP 705) for read-only instance attributes:-
PEP 705 was open to the idea of
ReadOnlybeing expanded to other contexts, although that proposal conforms to a completely frozen dataclass rather than a per-field spec (e.g. in Static-onlyfrozendata classes (or other ways to avoid runtime overhead) - Typing - Discussions on Python.org). -
PEP 705 also rejected the idea of reusing the
Finalannotation for behaving differently in different contexts. This is especially true of dataclass-likes created using@dataclass_transform, which may not require explicit decorators, causing confusion:# base_model.py from typing_extensions import dataclass_transform @dataclass_transform() class BaseModel: ...from typing_extensions import Final from base_model import BaseModel class A(BaseModel): a: Final[int] = 0 # Instance variable under the proposed `Final` changes class B: b: Final[int] = 0 # Remains a class variable
-
Preventing Final from meaning both (1) read-only instance variables and (2) non-overridable dataclasses fields in subclasses would allow the future ability to specify field defaults in subclasses without affecting read-only status.
Concept draft
import typing_extensions as _t
import dataclasses as _dc
@_dc.dataclass
class Data:
a: _t.ReadOnly[int] # Read-only dataclass field with no default
b: _t.ReadOnly[int] = 0 # Read-only field with default
c: int = 0 # Normal field
d: _t.Final[int] = 0 # Non-overridable class variable
e: _t.Final[_dc.Field[int]] = 0 # Non-overridable field, instance variable is **not** read-only
f: _t.Final[_t.ReadOnly[_dc.Field[int]]] = 0 # Non-overridable field which is read-only in instances
@_dc.dataclass
class Sub(Data):
b: _t.ReadOnly[int] = 1 # Default changed, instance variables are still read-only
c: _t.ReadOnly[int] = 1 # Mutable override error