non-pep695-generic-class (UP046)
Added in 0.12.0 · Related issues · View source
Derived from the pyupgrade linter.
Fix is sometimes available.
What it does
Checks for use of standalone type variables and parameter specifications in generic classes.
Why is this bad?
Special type parameter syntax was introduced in Python 3.12 by PEP 695 for defining generic classes. This syntax is easier to read and provides cleaner support for generics.
In particular, old-style TypeVar variables are typically allocated at module scope, but their
semantic meaning is only valid within the context of a generic class, function, or type alias.
PEP 695 eliminates this source of confusion by declaring type parameters at their point of
use.
Known problems
The rule currently skips generic classes nested inside of other functions or classes. It also
skips type parameters with the default argument introduced in PEP 696 and implemented in
Python 3.13.
This rule can only offer a fix if all of the generic types in the class definition are defined in the current module. For external type parameters, a diagnostic is emitted without a suggested fix.
Not all type checkers fully support PEP 695 yet, so even valid fixes suggested by this rule may cause type checking to fail.
Fix safety
This fix is marked as unsafe, as PEP 695 uses inferred variance for type parameters, instead
of the covariant and contravariant keywords used by TypeVar variables. As such, replacing
a TypeVar variable with an inline type parameter may change its variance.
Examples
Use instead:
In cases where you've intentionally defined a reusable TypeVar to share
the bounds across multiple uses:
from typing import Generic, TypeVar
ReusableT = TypeVar("ReusableT", bound=int | str | dict[int, str])
class GenericClass1(Generic[ReusableT]): ...
class GenericClass2(Generic[ReusableT]): ...
class GenericClass3(Generic[ReusableT]): ...
You can instead extract the bound as a type alias to retain both the benefits of the PEP 695 syntax and the reuse of the bound:
type ReusableTBound = int | str | dict[int, str]
class GenericClass1[ReusableT: ReusableTBound]: ...
class GenericClass2[ReusableT: ReusableTBound]: ...
class GenericClass3[ReusableT: ReusableTBound]: ...
See also
This rule replaces standalone type variables in classes but doesn't remove
the corresponding type variables even if they are unused after the fix. See
unused-private-type-var for a rule to clean up unused
private type variables.
This rule will not rename private type variables to remove leading underscores, even though the
new type parameters are restricted in scope to their associated class. See
private-type-parameter for a rule to update these names.
This rule will correctly handle classes with multiple base classes, as long as the single
Generic base class is at the end of the argument list, as checked by
generic-not-last-base-class. If a Generic base class is
found outside of the last position, a diagnostic is emitted without a suggested fix.
This rule only applies to generic classes and does not include generic functions. See
non-pep695-generic-function for the function version.