InitVar and ClassVar

dataclasses · Advanced · 9 min

Constructors often need inputs that shouldn't be stored, and classes need constants that aren't per-instance data.

Two special annotations change how a name is treated.

`ClassVar[T]` marks a true class attribute: not a field, not in __init__, __repr__ or comparisons. Use it for constants and registries shared by all instances:

MAX_ITEMS: ClassVar[int] = 100

Without ClassVar, an annotated name with a default would become a per-instance field.

`InitVar[T]` marks an init-only pseudo-field: it becomes a parameter of __init__ and is passed to __post_init__ as an argument, in field order, but is not stored on the instance, not shown in repr, and not returned by fields().

@dataclass
class User:
    name: str
    password: InitVar[str]
    password_hash: str = field(init=False)
    def __post_init__(self, password):
        self.password_hash = hash_it(password)

Typical uses: raw inputs to transform (a password to hash, a path to load), a database handle used only to fetch a value, or a flag that changes initialisation.

Note that replace() must be given every InitVar without a default, because it calls __init__ again and InitVars aren't stored.

Example

import hashlib
from dataclasses import InitVar, dataclass, field, fields
from typing import ClassVar

@dataclass
class Account:
    username: str
    password: InitVar[str]
    password_hash: str = field(init=False, repr=False)
    MIN_LENGTH: ClassVar[int] = 8                 # class constant, not a field

    def __post_init__(self, password: str) -> None:
        if len(password) < self.MIN_LENGTH:
            raise ValueError("password too short")
        self.password_hash = hashlib.sha256(password.encode()).hexdigest()[:12]

acct = Account("ada", "correct horse")
print(acct)                                 # Account(username='ada')
print(acct.password_hash)                   # 4104d36f8da2
print([f.name for f in fields(acct)])       # ['username', 'password_hash']
print(hasattr(acct, "password"))            # False: InitVar is not stored

Remember

  • ClassVar annotations are class attributes, not fields
  • InitVar values go to __init__ and __post_init__ but aren't stored
  • fields() lists neither ClassVar nor InitVar pseudo-fields

Watch out for

  • Forgetting ClassVar on a class constant, which turns it into a constructor parameter.

Check yourself

  1. What happens to an InitVar[str] argument after construction?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/dataclasses.html#init-only-variables

Builds on

Unlocks

Nothing builds on this yet.