Should non-generic classes with `__class_getitem__` be subscriptable in a type expression?

Quantity is a subclass of numpy.ndarray which is a subclass of Generic in the stubs, but not at runtime:

>>> import numpy as np
>>> np.ndarray.mro()
[<class 'numpy.ndarray'>, <class 'object'>]

IIUC, a type checker would consider Quantity as Generic and have inherited 2 type parameters with defaults.

Question
If u.km is not assignable to tuple[int, ...] [1], is a type checker expected to ignore the issue because of a __class_getitem__?


The runtime implementation makes me think they’d be better served by (PEP 835: Shorthand syntax for Annotated type metadata).

Using preserving-units as an example, something like this possible today and not much of a mouthful [2] if you alias some symbols:

Show a few generics
from typing import Annotated as A, Literal as L

import numpy as np


class Quantity(np.ndarray): ...


class Unit[T]:
    def __init__(self, arg: T) -> None:
        self.arg: T = arg


class PhysicalType[T]:
    def __init__(self, arg: T) -> None:
        self.arg: T = arg


type m = Unit[L["m"]]
type length = PhysicalType[L["length"]]
>>> A[Quantity, m]
typing.Annotated[__main__.Quantity, m]


>>> A[Quantity, length]
typing.Annotated[__main__.Quantity, length]

  1. _ShapeT_co’s bound ↩︎

  2. which seems like the goal for defining __class_getitem__ in the first place ↩︎

1 Like