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
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
ClassVarandFinal.
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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With