Both mypy and pyright support it, although mypy does not infer the type from the assignment, while it does for Final (as per the spec). Playgrounds: pyright, mypy.
Aligning it being allowed is probably a good thing here, but ruff as a linter[1] might still want to flag this (as it does for many other annotations) as this could result in inference-defined (allowed to diverge across type checkers) behavior in an exported type.
I’m aware the issue is actually for red-knot, the in-progress type checker, but answering holistically about what is allowed in the type system versus flaggable for a reason. ↩︎
Should the inferred type be int or Literal[1]? I think the reason it makes sense to allow bare Final is that it’s sensible to always infer the narrowest type, but with ClassVar that’s not the case.
If (and this is a big if IMO) we’re at the point of specifying inference behavior, in the absence of Final, a literal value should be inferred as the type of the literal, not as the more constrained literal type (so in this case int)
If anyone thinks the alternative, what would be the purpose of it not being Final if it’s a constant (can only be that exact literal value)?
I do not think we should attempt to specify inference as part of this issue. If we decide that bare ClassVar should be allowed, then it should be specified simply as “type is inferred as some type to which the RHS, if any, is assignable.” (I think mypy’s current behavior of choosing Any should thus also be allowable under this specification.)
We also should specify the behavior of bare ClassVar with no RHS; both mypy and pyright currently agree that this is not an error and the inferred type is Any/Unknown.
Allow bare ClassVar to be used: if an assigned value is available, the type should be inferred as some type to which this value is assignable. If no assigned value is available, the type
should be inferred to an unknown static type (such as Any).
PR:
Does this require an approval from the typing council?
I suppose type checkers should treat a bare ClassVar similar to an unannotated global. Since we already have that concept, it makes sense to me to allow bare ClassVar with the expectation that type checkers will use similar inference.
Glad I found this post. I wanted to ask about this exactly.
In real-world scenario, my use-case is to explicitly mark ClassVars as such on vars where the inferred type was already correct, but not marked as a ClassVariable.
I’ve been doing this a lot in distutils/setuptools. For example:
class Distribution:
common_usage = """\
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
setup.py install will install the package
"""
display_option_names = [translate_longopt(x[0]) for x in display_options]
I’d like to be able to do:
class Distribution:
common_usage: ClassVar = """\
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
setup.py install will install the package
"""
display_option_names: ClassVar = [translate_longopt(x[0]) for x in display_options]
Instead of:
class Distribution:
common_usage: ClassVar[str] = """\
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
setup.py install will install the package
"""
display_option_names: ClassVar[list[str]] = [translate_longopt(x[0]) for x in display_options]
If the infered type was the literal (like Final does), then for me that’d defeat the purpose of wanting implicit annotation.
Also if your classvar is only allowed to be a specific Literal, shouldn’t you use Final? Either way a subclass changing the value would be in violation.