could you help me understand why I am getting the TypeError: 'type' object is not subscriptable
error with the code below?
Maybe I'm getting this wrong, but as I understood the Color
type annotation in the filter()
function is saying that the function will result in an Iterable
of Color
, which is exactly what I want. But when I try to annotate the function I get the error. ( but the waty, how come a type annotation is preventing the program to run? I thought that type hints in in Python would just matter inside your IDE, not in runtime).
Any light on this would be much appreciated.
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import TypeVar, Any, Generic, Iterator, Iterable
from abc import ABC, abstractmethod
from dataclasses import dataclass
T = TypeVar('T', bound=Any)
I = TypeVar('I', bound=Any)
class AbstractGenerator(ABC, Iterator[T], Generic[T, I]):
def __init__(self):
super().__init__()
self._items = None
self._next_item = None
@property
def items(self) -> Any:
return self._items
@items.setter
def items(self, items: Any) -> AbstractGenerator:
self._items = items
return self
@property
def next_item(self) -> Any:
return self._next_item
@next_item.setter
def next_item(self, next_item: Any) -> AbstractGenerator:
self._next_item = next_item
return self
@abstractmethod
def __len__(self) -> int:
pass
@abstractmethod
def __iter__(self) -> Iterable[T]:
pass
@abstractmethod
def __next__(self) -> Iterable[T]:
pass
@abstractmethod
def __getitem__(self, id: I) -> Iterable[T]:
pass
ColorId = int
@dataclass(frozen=True)
class Color:
id: ColorId
name: str
class MyColorsGenerator(AbstractGenerator[Color, int]):
def __init__(self):
super().__init__()
self._colors: list[Color] = []
self._next_color_index: int = 0 #None
@property
def colors(self) -> list[Color]:
return self._colors
@colors.setter
def colors(self, colors: list[Color]) -> MyColorsGenerator:
self._colors = colors
return self
@property
def next_color_index(self) -> int:
return self._next_color_index
@next_color_index.setter
def next_color_index(self, next_color_index: int) -> MyColorsGenerator:
self._next_color_index = next_color_index
return self
def add_color(self, color: Color) -> MyColorsGenerator:
self.colors.append(color)
return self
def __len__(self) -> int:
return len(self.colors)
def __iter__(self) -> Iterable[Color]:
return self
def __next__(self) -> Iterable[Color]:
if self.next_color_index < len(self.colors):
self.next_color_index += 1
return self.colors[self.next_color_index - 1]
else:
raise StopIteration
def __getitem__(self, id: ColorId) -> Iterable[Color]:
return list(filter[Color](lambda color: color.id == id, self.colors))
colors_generator: MyColorsGenerator = MyColorsGenerator()
colors_generator \
.add_color(Color(id=0, name="Blue")) \
.add_color(Color(id=1, name="Red")) \
.add_color(Color(id=2, name="Yellow")) \
.add_color(Color(id=3, name="Green")) \
.add_color(Color(id=4, name="White")) \
.add_color(Color(id=5, name="Black"))
# This results in: TypeError: 'type' object is not subscriptable
#colors: Optional[list[Color]] = list(filter[Color](lambda color: color.id == 4, colors_generator))
# This works, notice the only thing I did was to remove the type annotation for the expected generic type ([Color])
colors: Optional[list[Color]] = list(filter(lambda color: color.id == 4, colors_generator))
print(colors)
The issue is that generics aren't a language-level addition, but a library one. Specifying the generic type parameters actually employs the same []
operator you use for item access in collections, except it is defined on the metaclass. For this reason the generics syntax originally only worked with specific classes in the typing
module (typing.List[int]
, typing.Dict[str, str]
, etc.). Since python3.9, however, some common classes from the standard library have been extended to support the same operation, for brevity, like list[int]
, dict[str, str]
. This is still NOT a language feature, and most classes in the standard library do not implement it. Moreover, as you've rightfully noticed, these annotations carry (almost) no meaning for the interpreter, and are (mostly) just there for the ide. Among other things, that implies that you don't instantiate generic classes as specialized generics (list()
is correct, list[int]()
is legal, but pointless and considered a bad practice). filter
is a class in the standard library, which does not provide the generic-aliasing []
operation, so you get the error that applying it is not implemented ("'type' object is not subscriptable
", filter
is an instance of type
, and []
is the subscription operator). Python as the language does not understand the concept of a generic, and so it cannot give you a better error message like "'filter' is not a generic class
". Even if it was, however, you shouldn't have invoked it this way.
A special note should be made about generic functions. They CANNOT be explicitly supplied with generic parameters. So, if instead of filter
we were talking about some function like:
T = typing.TypeVar("T")
def my_filter(f: typing.Callable[[T], bool], seq: list[T]) -> list[T]:
...
, there would have been no way to explicitly tell you're interested in my_filter[Color]
.
TL;DR: filter
is not a generic class in terms of type annotations, so it does not support the []
operation