95

I've just begun playing around with Python's Data Classes, and I would like confirm that I am declaring Class Variables in the proper way.

Using regular python classes

class Employee:

    raise_amount = .05

    def __init__(self, fname, lname, pay):
        self.fname = fname
        self.lname = lname
        self.pay = pay

Using python Data Class

@dataclass
class Employee:
    fname: str
    lname: str
    pay: int
    raise_amount = .05

The class variable I am referring to is raise_amount. Is this a properly declared class variable using Data Classes? Or is there a better way of doing so?

I have tested the data class implementation already and it provides the expected functionality, but I am mainly wondering if my implementation is following best practices.

0

2 Answers 2

145

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 is typing.ClassVar. If a field is a ClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. Such ClassVar pseudo-fields are not returned by the module-level fields() function.

Sign up to request clarification or add additional context in comments.

4 Comments

My original setup raise_amount = .05 appears to have the same functionality as raise_amount: ClassVar[float] = .05. If the data type is missing, do you know if it automatically assumes a variable is a class variable? I do like how your implementation is more explicit though.
If a variable is not annotated, it is ignored by dataclass completely. That effectively makes it a class variable as well.
Got it, makes sense. Thank you. I think it's better to keep things obvious, so your method is probably the way to go. Much appreciated.
Worth noting, dataclasses.fields(Foo) won't show cvar, but it will be in Foo.__dataclass_fields__
9

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

2 Comments

...and it is a good thing that you can not assign to class variables via instance! Standard Python should have that feature.
@RobertSiemer Actually there is a pythonic pattern where class vars act like default values. If you don’t want the default value you can overwrite it with an instance var.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.