[Spec] Modifying a `ClassVar` on an instance

According to the typing spec:

The typing.ClassVar type qualifier is used to annotate class variables that should not be set on class instances.

However, what about modifying the ClassVar with __setitem__ or __setattr__ on an instance? It feels like this should also not be allowed. Two examples:

__setitem__ example: pyright playground, mypy playground, ty playground

from typing import ClassVar

class Foo:
    d: ClassVar[dict[str, object]] = {}

    def set_value(self, key: str, value: object) -> None:
        self.d[key] = value  # <-- Use Foo.d[key] = value instead?!

__setattr__ example: pyright playground, mypy playground, ty playground

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

class Bar:
    p: ClassVar[Point] = Point(0.0, 0.0)

    def set_x(self, value: float) -> None:
        self.p.x = value  # <-- Use Bar.p.x = value instead?!

Enforcing that setting attributes/items should only be done on the class and not the instance level would be a good reminder that such modification does indeed apply to all instances.

1 Like

It’s more intention revealing to write it your way. And even to refactor both methods into classmethods. It’d be a great optional rule for a linter to warn about. And for a different ClassOnlyVar typequalifier.

But this would prohibit combining classes with ClassVars marked with mixins that use those class vars. A lot of extensible code will deliberately refer to an class variable on self, to let a subclass override it with an instance variable. It might also wish to use ClassVar, simply to prevent accidental reassignment.

Do you have an example? Note that the idea of this proposal is to flag

self.class_var.x.y.z[...] = ...
self.class_var.x.y.z = ...

Simply accessing/reading the ClassVar via self.class_var is not affected in any way.
(Basically, ClassVars that are accessed on an instance should be treated as immutable).

I believe networkx used to (and probably still does) do this. I agree with you however, and think it’s simply poor design.