Skip to content

Accept nesting Final in ClassVar for dataclassesΒ #18235

Open
@ncanceill

Description

Bug Report

[Python 3.13] mypy does not accept nesting ClassVar and Final in class body

The official docs of the typing module say:

Changed in version 3.13: ClassVar can now be nested in Final and vice versa.

The typing specs on qualifiers are more specific:

Final may be wrapped only by other type qualifiers (e.g. ClassVar or
Annotation).

In particular, nesting ClassVar and Final is a special case for dataclasses:

Type checkers should infer a final attribute that is initialized in a class
body as being a class variable, except in the case of 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.

To Reproduce

In both classes below the foo statement is not accepted by mypy:

from dataclasses import dataclass
from typing import ClassVar, Final

@dataclass
class Bar:
    foo: ClassVar[Final[int]] = 0

@dataclass
class Baz:
    foo: Final[ClassVar[int]] = 0

Note that the docs only mention ClassVar[Final[int]] and not Final[ClassVar[int]]. In fact, the latter is not considered a class variable by the current dataclasses implementation:

>>> Bar(foo=1)  # foo is a class variable
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    Bar(foo=1)
    ~~~^^^^^^^
TypeError: Bar.__init__() got an unexpected keyword argument 'foo'
>>> Baz(foo=1)  # foo is an instance variable
Baz(foo=1)

Expected Behavior

[Python 3.13] mypy should accept ClassVar[Final[<type>]] in the body of a dataclass

The form ClassVar[Final[int]] conforms with the official typing specs and behaves as expected. I think it should be accepted as part of Python 3.13 support.

The typing specs allow for a warning or error when ClassVar and Final are nested outside of a dataclass body. I think this should result in an error since it is redundant, perhaps only in strict mode.

For the form Final[ClassVar[int]] I am not sure. It could either result in an error or simply be parsed as a final instance variable.

Actual Behavior

The form ClassVar[Final[int]] results in:
error: Final can be only used as an outermost qualifier in a variable annotation [valid-type]

The form Final[ClassVar[int]] results in:
error: Variable should not be annotated with both ClassVar and Final [misc]

The behavior is the same with or without the @dataclass decorator.

Your Environment

  • Mypy version used: 1.13.0
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files): strict = true
  • Python version used: 3.13.0

Notes

The typing specs were changed in python/typing#1669

Related discussion is found in python/cpython#89547

The difference between ClassVar[Final[int]] and Final[ClassVar[int]] was noticed in microsoft/pyright#8676 (comment)

This issue is about Python 3.13 so could be added to #17264

This issue is related to #12061

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions