Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does slots not work with final dataclass attribute?

I have a dataclass with slots=True and a final class variable/constant that is used as version identifier. For some reason that does not work as expected. I either need to remove the slots or change Final to ClassVar.

Here is a minimal example:

#!/usr/bin/env python3

from __future__ import annotations
from dataclasses import dataclass
from typing import Final, ClassVar

@dataclass(slots=True)
class Child():
    VERSION: Final[str] = '1.0.0'

    @classmethod
    def check_version(cls, version: str) -> bool:
        return version == cls.VERSION

print(Child.check_version('1.0.0'))
# Returns False

Could anyone explain me why this happens and how to fix it properly? I am using python 3.11.7

like image 755
NicoHood Avatar asked Jun 23 '26 22:06

NicoHood


1 Answers

The easiest runtime fix is to do the following:

from __future__ import annotations

import typing as t
from dataclasses import dataclass

if t.TYPE_CHECKING:
    from typing import Final
else:
    from typing import ClassVar as Final

@dataclass(slots=True)
class Child:
    VERSION: Final[str] = "1.0.0"

    @classmethod
    def check_version(cls, version: str) -> bool:
        return version == cls.VERSION
>>> print(Child.check_version("1.0.0"))
True

typing.Final is a stricter version of typing.ClassVar, indicating to type-checkers that a class variable cannot be overridden. From PEP 591:

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.

As part of @dataclasses.dataclass's magic determination of instance variables (as opposed to class variables) when it makes its constructor method (__init__), it does runtime introspection to exclude typing.ClassVar-annotated variables. Unfortunately, as of today, the introspection procedure doesn't consider typing.Final-annotated variables, which looks like an implementation bug.

If you're using mypy - unfortunately, mypy also doesn't pick up that Final indicates a class variable and not an instance variable when using @dataclasses.dataclass(slots=True), which is also a bug:

@dataclass(slots=True)
class Child:
    VERSION: Final[str] = "1.0.0"

    @classmethod
    def check_version(cls, version: str) -> bool:
        return version == cls.VERSION  # mypy: "VERSION" in __slots__ conflicts with class variable access  [misc]
like image 150
dROOOze Avatar answered Jun 25 '26 11:06

dROOOze



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!