One of the new features of Python 3.10 is TypeAlias. The example given is as follows.

1
2
StrCache = 'Cache[str]' # a type alias
LOG_PREFIX = 'LOG[DEBUG]' # a module constant

It can be written as.

1
2
StrCache: TypeAlias = 'Cache[str]' # a type alias
LOG_PREFIX = 'LOG[DEBUG]' # a module constant

This makes StrCache more of a type alias than a string variable that clearly looks like Cache[str] (which it is).

This article is not about TypeAlias per se, but rather about Cache[str], which shows that Python seems to support Java-like generics, like Python’s built-in support for List[str] or list[str].

So let’s see how Python can implement a Cache[str] Cache that can only hold strings, but not other types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

from typing import TypeVar, Generic
 
T = TypeVar('T')
 
class Cache(Generic[T]):
    def __init__(self) -> None:
        self.items: dict[str, T] = {}
 
    def put(self, key: str, item: T) -> None:
        self.items[key] = item                  # Line 13
 
    def get(self, key: str) -> T:
        return self.items.get(key)

We are still using Python’s typing module, so the above code is still in the realm of class type hints, which is not a constraint on the Python interpreter and only affects checking tools like mypy.

using the above class.

1
2
3
4
5
6
7
cache = Cache[str]()
 
cache.put('a', 'abc')
print(cache.get('a'))
 
cache.put('b', 123)                           # Line 21
print(cache.get('b'))

The command python test.py works fine and the output is as follows.

1
2
abc
123

But in IntelliJ IDEA, the fifth line above, the cache.put('b', 123) line, prompts:

1
Expected type 'str' (matched generic type 'T'), got 'int' instead

If you use mypy to detect.

1
2
3
4
$ mypy tt.py
tt.py:13: error: Incompatible return value type (got "Optional[T]", expected "T")
tt.py:21: error: Argument 2 to "put" of "Cache" has incompatible type "int"; expected "str"
Found 2 errors in 1 file (checked 1 source file)

In addition to Generic, TypeVar used above, typing module has more type constraints like Mapping, Iterator, Sequence and more Java-like generic functions.

Generic subtypes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from typing import TypeVar, Generic
 
T = TypeVar('T')
 
class Cache(Generic[T]):
    pass
 
 
class TTLCache(Cache[T]):
    pass

Generic functions

1
2
3
4
5
6
from typing import TypeVar, Sequence
 
T = TypeVar('T')      # Declare type variable
 
def first(seq: Sequence[T]) -> T:   # Generic function
    return seq[0]

There are more uses than can be listed here. The need to write your own implementation code using generics is not really big, unless you do a third-party generic library that will use generic writing, which makes it clearer for others to use.