GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — spec: clarify interaction of Final and dataclass by carljm · Pull Request #1669 · python/typing

Conversation

@carljm

@carljm carljm commented Mar 29, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

See discussion thread at https://discuss.python.org/t/treatment-of-final-attributes-in-dataclass-likes/47154 and issue at python/cpython#89547

Consider a dataclass with a Final-annotated initialized assignment in the class body:

@dataclass
class C:
    x: Final[int] = 3

This is currently under-specified in the typing spec. Neither PEP 591 or PEP 681 clearly specified this composition.

There are two possible consistent interpretations:

  1. It could be a class-only variable (implicit ClassVar) C.x with final value 3. In this interpretation, it should not be a dataclass field, because dataclass fields are inherently set on instances, they cannot be ClassVar. This is the interpretation suggested by PEP 591 (and thus also the current typing spec for Final), which say that Final with an assigned value in a class body is always implicitly a ClassVar.

  2. It could be a dataclass field x with default value 3, which cannot be reassigned on an instance after it is initialized in the generated __init__ method.

This pull request specifies option 2.

I will also provide an update to the conformance suite for this, if the Typing Council accepts the spec change.

Current runtime behavior

The stdlib dataclasses module considers x in this example to be a dataclass field.

If the assigned value is a default value (as in the example above), dataclasses leaves that default value in place as a class attribute (so x is a class-and-instance variable, and C.x == 3.) If the assigned value is a field(...) call, dataclasses does not leave any attribute x on the class at all, so x is purely a dataclass field / instance variable.

Current type-checker behavior

Playground links:

mypy
pyre
pyright

All three agree with the runtime behavior of dataclasses, considering x to be a dataclass field which is included in the dataclass __init__ method and set on instances in __init__. All three prohibit further assignments to x on instances, noting that it is a final attribute. Pyright also mentions that it is a ClassVar, which is confusing given that "ClassVar" and "dataclass field" are (or should be) mutually exclusive.

All three assume that C.x is available and of type int, whether we have x: Final[int] = 3 or x: Final[int] = field(default_value=3). That is, none of the type checkers model the actual runtime behavior that field() objects are removed from the class and not replaced with anything. (Arguably this behavior is just a bug/inconsistency in the dataclasses runtime implementation.)

Considerations

  • Option 2 (proposed in this PR) is clearly less disruptive to existing code, as it effectively just specifies the de facto current behavior of the runtime and all type checkers. The only practical change to type checker behavior in the given examples that would occur as a result of this spec change is that Pyright should not claim in its error message that x is a ClassVar, but otherwise behavior would remain as it is; errors would still be raised on exactly the same lines by all three type checkers.
  • Option 2 has the downside that one cannot assume as an invariant that the syntax x: Final[int] = 3 always means that x will always have value exactly 3. In dataclass bodies this will be more nuanced: the class attribute will always be 3, but it will be shadowed by an instance attribute that may have a different value on any given instance. On the other hand, it is already the case that the semantics of annotated assignments in dataclass bodies differ from other contexts: x: int = field(...) when field(...) does not return int would obviously be a type error anywhere else. This example illustrates that in dataclasses, the annotation generally does not apply to the immediate assignment, but to the type of the dataclass field (instance var) specified by the annotated assignment.
  • If we select Option 1, it will be quite cumbersome to achieve a Final dataclass field (one would have to write out the __init__ method manually instead of allowing dataclass to generate it, eliminating a key benefit of dataclasses.) If we select Option 2, it remains quite easy to have a final classvar on a dataclass, using a ClassVar[Final[...]] annotation.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Enegg, tmke8, andrei-korshikov, and rj-xu reacted with thumbs up emoji
All reactions
  • 👍 4 reactions
@carljm
clarify interaction of Final and dataclass
8a6f51b
@carljm carljm mentioned this pull request Mar 29, 2024
Clarify meaning of Final in dataclasses python/cpython#89547
Open
@carljm carljm changed the title clarify interaction of Final and dataclass spec: clarify interaction of Final and dataclass Mar 29, 2024
@Tinche

Tinche commented Mar 29, 2024

Copy link
Copy Markdown

This will likely affect attrs too so I'm following the discussion. I'm personally rooting for option 2.

A small nit:

If the assigned value is a default value (as in the example above), dataclasses leaves that default value in place as a class attribute

That only seems to be true for non-slots dataclasses. If the dataclass is created using slots=True (and this is the default in attrs, for example), C.x will be a slot descriptor instead.

carljm, mikeshardmind, Daverball, Enegg, sobolevn, and andrei-korshikov reacted with thumbs up emoji
All reactions
  • 👍 6 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bzoracler bzoracler mentioned this pull request Mar 29, 2024
Add ability to use Final in a field type annotation pydantic/pydantic#2768
Merged
4 of 5 tasks
@srittau srittau added the Typing Council decision Needs to be approved by the Typing Council. Do not merge until approved. label Mar 29, 2024
erictraut added a commit to microsoft/pyright that referenced this pull request Mar 31, 2024
@erictraut
Changed the handling of Final variables (that are not explicitly ma…
b085a26
…rked `ClassVar`) within dataclass class bodies. This is consistent with the runtime and [this proposed change to the typing spec](python/typing#1669).
@erictraut

erictraut commented Mar 31, 2024

Copy link
Copy Markdown
Collaborator

Thanks for the PR. Personally, I agree that option 2 is the preferred option, and I like the wording in the proposed typing spec change.

I also agree that pyright's current error message is misleading (doesn't match the runtime behavior), so I've updated pyright accordingly so it doesn't mention ClassVar in this situation.

If you don't get any further feedback on the PR within the next few days, the next step is to create an issue in the typing council repo asking for a formal decision about the spec update.

If this is approved, we'll want to augment the compliance tests to cover the new cases. This could be done as part of the same PR or a separate PR.

carljm reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@carljm carljm mentioned this pull request Apr 3, 2024
Typing spec update for Final and dataclasses python/typing-council#24
Closed
5 tasks done
JelleZijlstra
JelleZijlstra reviewed Apr 3, 2024
View reviewed changes
Comment thread docs/spec/qualifiers.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

@carljm
Jelle comments
0e3f3a7
JelleZijlstra
JelleZijlstra approved these changes Apr 4, 2024
View reviewed changes
hauntsaninja
hauntsaninja approved these changes Apr 4, 2024
View reviewed changes
@JelleZijlstra
JelleZijlstra merged commit 7a70cd6 into python:main Apr 11, 2024
JelleZijlstra pushed a commit to JelleZijlstra/typing that referenced this pull request Apr 11, 2024
@carljm @JelleZijlstra
spec: clarify interaction of Final and dataclass (python#1669)
4110874
@carljm

carljm commented Apr 22, 2024

Copy link
Copy Markdown
Member Author

(btw, I am still planning to add conformance tests for this)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@carljm
carljm deleted the dcfinal branch April 22, 2024 21:24
@tmke8 tmke8 mentioned this pull request May 13, 2024
Allow omitting initializer for Final attributes in dataclasses python/mypy#5608
Closed
@carljm carljm mentioned this pull request May 20, 2024
Add conformance tests for Final dataclass fields #1741
Merged
@Viicos Viicos mentioned this pull request Jul 16, 2024
Clarify signification of Annotation #1805
Closed
This was referenced Nov 15, 2024
(🐞) false positive mypy error "Only root is allowed as a field of a RootModel" pydantic/pydantic#10474
Closed
Bare Final special form with dataclasses microsoft/pyright#9468
Closed
@ncanceill ncanceill mentioned this pull request Dec 3, 2024
Accept nesting Final in ClassVar for dataclasses python/mypy#18235
Closed
@Viicos Viicos mentioned this pull request Dec 15, 2024
Fields marked as Final with a default value shouldn't be considered as class variables pydantic/pydantic#11119
Closed
3 tasks done
@Viicos Viicos mentioned this pull request Feb 25, 2025
Order of Final annotation changes behaviour of field pydantic/pydantic#9904
Closed
1 task done
@Viicos Viicos mentioned this pull request May 26, 2025
ClassVar[Final[T]] should be allowed microsoft/pyright#10500
Closed
This was referenced Dec 26, 2025
Fix a false positive for class attribute typed with Final pylint-dev/pylint#10712
Merged
Pylint C0103 (invalid-name) for dataclass Final field pylint-dev/pylint#10790
Closed
@davidhalter davidhalter mentioned this pull request Oct 21, 2026
ClassVar + Final #2371
Open
@Viicos Viicos mentioned this pull request Oct 31, 2026
Remove functionality deprecated during the 2.x timeline pydantic/pydantic#14158
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@JelleZijlstra JelleZijlstra JelleZijlstra approved these changes
@hauntsaninja hauntsaninja hauntsaninja approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Typing Council decision Needs to be approved by the Typing Council. Do not merge until approved.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

6 participants

@carljm @Tinche @erictraut @JelleZijlstra @hauntsaninja @srittau
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.