To create a class variable, annotate the field as a typing.ClassVar or not at all.
from typing import ClassVar
from dataclasses import dataclass
@dataclass
class Foo:
ivar: float = 0.5
cvar: ClassVar[float] = 0.5
nvar = 0.5
foo = Foo()
Foo.ivar, Foo.cvar, Foo.nvar = 1, 1, 1
print(Foo().ivar, Foo().cvar, Foo().nvar) # 0.5 1 1
print(foo.ivar, foo.cvar, foo.nvar) # 0.5 1 1
print(Foo(), Foo(12)) # Foo(ivar=0.5) Foo(ivar=12)
There is a subtle difference in that the unannotated field is completely ignored by @dataclass, whereas the ClassVar field is stored but not converted to an attribute.
dataclassesโ Data Classes
The member variables [...] are defined using PEP 526 type annotations.
Answer from user5349916 on Stack OverflowClass variables
One of two places where
dataclass()actually inspects the type of a field is to determine if a field is a class variable as defined in PEP 526. It does this by checking if the type of the field istyping.ClassVar. If a field is aClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. SuchClassVarpseudo-fields are not returned by the module-level fields() function.
To create a class variable, annotate the field as a typing.ClassVar or not at all.
from typing import ClassVar
from dataclasses import dataclass
@dataclass
class Foo:
ivar: float = 0.5
cvar: ClassVar[float] = 0.5
nvar = 0.5
foo = Foo()
Foo.ivar, Foo.cvar, Foo.nvar = 1, 1, 1
print(Foo().ivar, Foo().cvar, Foo().nvar) # 0.5 1 1
print(foo.ivar, foo.cvar, foo.nvar) # 0.5 1 1
print(Foo(), Foo(12)) # Foo(ivar=0.5) Foo(ivar=12)
There is a subtle difference in that the unannotated field is completely ignored by @dataclass, whereas the ClassVar field is stored but not converted to an attribute.
dataclassesโ Data Classes
The member variables [...] are defined using PEP 526 type annotations.
Class variables
One of two places where
dataclass()actually inspects the type of a field is to determine if a field is a class variable as defined in PEP 526. It does this by checking if the type of the field istyping.ClassVar. If a field is aClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. SuchClassVarpseudo-fields are not returned by the module-level fields() function.
You should annotate class variable with typing.ClassVar for mypy to capture errors when a class variable is assigned a value via an instance of the class.
Leaving out annotation completely won't help mypy.
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Class:
name: ClassVar[str]
desc = "default description"
instance = Class()
instance.desc = "instance description"
instance.name = "instance name"
Class.name = "class name"
Class.desc = "class description"
Mypy output;
error: Cannot assign to class variable "name" via instance
I have the following code:
from dataclasses import dataclass @dataclass class A: a: int @dataclass(frozen=True) class B: b: A obj = B(1) print(type(obj.b)) # == int, not A
Is it possible to have obj.b be of type A?
Edit: I mean without doing obj = B(A(1), perhaps sth with field()?
I have an unusual use case where I have a dataclass with a bunch of attributes. Each attribute optionally contains multiple function pointers using the dataclass.field metadata dictionary.
My main script is iterating over around million data rows, putting the data into this dataclass, and then needs to figure out which function to call for each attribute.
I originally created a class method to return a dictionary of the attributes and their corresponding function. Example code:
@dataclassdef MyClass:attribute_a: str = field(metadata={'function_type_1': function_pointer_a,'function_type_2': function_pointer_b})attribute_b: str = field(metadata={'function_type_1': function_pointer_c,'function_type_2': function_pointer_d})
@classmethoddef get_function_type_1_dictionary(cls) -> dict:return {class_field.name: function_type_1for class_field in dataclass.fields(cls)if (function_type_1 := class_field.metadata.get('function_type_1'))}
This has worked, but I've noticed that it is a little bit slow since the class method is recreating the dictionary from scratch each time.
My idea to speed this up a bit, would be to make a class variable that contains the dictionary, and then create a class method to simply return this dictionary. This way, the dictionary would only be created once.
I know to create a class variable in a dataclass, I create an attribute and give it a typehint of ClassVar, but when I set it equal to a function defined lower in the class, it cannot resolve the reference to the function:
@dataclassdef MyClass:attribute_a: str = field(metadata={'function_type_1': function_pointer_a,'function_type_2': function_pointer_b})attribute_b: str = field(metadata={'function_type_1': function_pointer_c,'function_type_2': function_pointer_d})my_class_var: ClassVar[dict] = get_function_type_1_dictionary()
Is there a way to accomplish this? Or am I barking up the wrong tree?
It sure does work:
from dataclasses import dataclass
@dataclass
class Test:
_name: str="schbell"
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, v: str) -> None:
self._name = v
t = Test()
print(t.name) # schbell
t.name = "flirp"
print(t.name) # flirp
print(t) # Test(_name='flirp')
In fact, why should it not? In the end, what you get is just a good old class, derived from type:
print(type(t)) # <class '__main__.Test'>
print(type(Test)) # <class 'type'>
Maybe that's why properties are nowhere mentioned specifically. However, the PEP-557's Abstract mentions the general usability of well-known Python class features:
Because Data Classes use normal class definition syntax, you are free to use inheritance, metaclasses, docstrings, user-defined methods, class factories, and other Python class features.
A solution with minimal additional code and no hidden variables is to override the __setattr__ method to do any checks on the field:
@dataclass
class Test:
x: int = 1
def __setattr__(self, prop, val):
if prop == "x":
self._check_x(val)
super().__setattr__(prop, val)
@staticmethod
def _check_x(x):
if x <= 0:
raise ValueError("x must be greater than or equal to zero")