I'm guessing you wrote code that looked roughly like this?

class Matrix:
    T = TypeVar('T')

    def _matrix_map(self, mapper: Callable[[Tile], T]) -> Dict[Coordinate, T]:
        # ...snip...

There's actually no issue with this at runtime/no issue for mypy, but it seems pylint isn't happy with you putting that "T" inside the scope of the class definition. What you can do to satisfy pylint is to instead do this:

T = TypeVar('T')

class Matrix:
    def _matrix_map(self, mapper: Callable[[Tile], T]) -> Dict[Coordinate, T]:
        # ...snip...

This is exactly equivalent to the former code snippet, and will also satisfy pylint.

(As a tangent, I'd argue that the fact that pylint isn't happy with the former is actually a bug. To be fair though, there aren't really any established style guides on best practices for type hints yet/putting a typevar inside of a nested scope is a somewhat uncommon use case, so I guess I can't really blame them for not accounting for this case.)

The only downside is that having that TypeVar on the outside does seem vaguely untidy/seems like it'll pollute the global namespace. You can partially mitigate this by renaming it to something like _T for example, to indicate it's private. You can also reuse this typevar whenever you need another generic class or function, which should also hopefully help keep the namespace pollution down.

For more information about using generics with Python type hints, see http://mypy.readthedocs.io/en/stable/generics.html

Answer from Michael0x2a on Stack Overflow
๐ŸŒ
Python
typing.python.org โ€บ en โ€บ latest โ€บ reference โ€บ generics.html
Generics โ€” typing documentation
These are functions where the types of the arguments or return value have some relationship: from typing import TypeVar, Sequence T = TypeVar('T') # A generic function!
๐ŸŒ
Python documentation
docs.python.org โ€บ 3 โ€บ library โ€บ typing.html
typing โ€” Support for type hints
February 24, 2026 - Source code: Lib/typing.py This module provides runtime support for type hints. Consider the function below: The function surface_area_of_cube takes an argument expected to be an instance of float,...
Discussions

Specifying type for Generic Function
One solution is T = int, while other is T = list[int] | int. There's no rule right now on simplest type/which one is picked. I'm unsure a simplest type is even well defined/clear thing. So it'd be nice for ambiguous cases if there was a way to indicate to the generic function the intended type. More on github.com
๐ŸŒ github.com
3
2
November 13, 2021
Simplifying Parametrized Type Hints for Generic Functions in Python - Typing - Discussions on Python.org
Hi everyone, Iโ€™m looking for a way to simplify the type hints for a generic function. Specifically, I want to parametrize a type of a function, where this type itself is also parametrized, and I want to specify the latter differently for different variables. More on discuss.python.org
๐ŸŒ discuss.python.org
0
July 4, 2024
Generics, Type[T], and TypeVarTuple
I have a typing and API design question. With non-variadic generics, I can write something like the following: from typing import Generic, Type, TypeVar T = TypeVar("T") class Example(Generic[T]): atype: Type[T] def __init__(self, atype: Type[T]): self.atype = atype def get_value(self) -> T: ... More on discuss.python.org
๐ŸŒ discuss.python.org
2
0
September 4, 2023
python - Use of Generic and TypeVar - Stack Overflow
The whole purporse of using Generic and TypeVar (here represented as the X and Y variables) is when one wants the parameters to be as generic as possible. int can be used, instead, in this case. The difference is: the static analyzer will interpret the parameter as always being an int. Using generics mean the function ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
Top answer
1 of 2
11

I'm guessing you wrote code that looked roughly like this?

class Matrix:
    T = TypeVar('T')

    def _matrix_map(self, mapper: Callable[[Tile], T]) -> Dict[Coordinate, T]:
        # ...snip...

There's actually no issue with this at runtime/no issue for mypy, but it seems pylint isn't happy with you putting that "T" inside the scope of the class definition. What you can do to satisfy pylint is to instead do this:

T = TypeVar('T')

class Matrix:
    def _matrix_map(self, mapper: Callable[[Tile], T]) -> Dict[Coordinate, T]:
        # ...snip...

This is exactly equivalent to the former code snippet, and will also satisfy pylint.

(As a tangent, I'd argue that the fact that pylint isn't happy with the former is actually a bug. To be fair though, there aren't really any established style guides on best practices for type hints yet/putting a typevar inside of a nested scope is a somewhat uncommon use case, so I guess I can't really blame them for not accounting for this case.)

The only downside is that having that TypeVar on the outside does seem vaguely untidy/seems like it'll pollute the global namespace. You can partially mitigate this by renaming it to something like _T for example, to indicate it's private. You can also reuse this typevar whenever you need another generic class or function, which should also hopefully help keep the namespace pollution down.

For more information about using generics with Python type hints, see http://mypy.readthedocs.io/en/stable/generics.html

2 of 2
7

With the introduction of type parameter lists in Python 3.12, it is no longer necessary to explicitly define a TypeVar. (Note also that typing.Dict was deprecated in Python 3.9 since dict itself can used instead.)

from collections.abc import Callable


class Matrix:
    def _matrix_mapT -> dict[Coordinate, T]:
        ...

If the type parameter should be shared by all methods, it can also be declared at the class level.

class Matrix[T]:
    def _matrix_map(self, mapper: Callable[[Tile], T]) -> dict[Coordinate, T]:
        ...
๐ŸŒ
Medium
medium.com โ€บ @steveYeah โ€บ using-generics-in-python-99010e5056eb
Using Generics in Python. If you are using type hints in Pythonโ€ฆ | by SteveYeah | Medium
July 21, 2021 - Here we have added a generic type named T. We did this by using the TypeVar factory, giving the name of the type we want to create and then capturing that in a variable named T. This is then used the same way as any other type is used in Python type hints. T and U are commonly used names in generics (T standing for Type and U standing forโ€ฆ. nothing. Itโ€™s just the next letter in the alphabet) similar to how i and x are used as iteration variables. Using this generic type T, the first() function now states that the container parameter is a list of a โ€œgeneric typeโ€. ...
๐ŸŒ
Mypy
mypy.readthedocs.io โ€บ en โ€บ stable โ€บ generics.html
Generics - mypy 1.20.0 documentation
Here is the same example using the legacy syntax (Python 3.11 and earlier): from typing import TypeVar, Sequence T = TypeVar('T') # A generic function!
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python โ€บ python-generics
Python Generics - GeeksforGeeks
October 29, 2025 - So, normally, "generic" functions would just verify if the parameters or objects had the bare minimum of required properties and then handle the data accordingly. Example : In below code, Python generics enable type constraints for functions like process_data, ensuring that inputs conform to expected types; meanwhile, duck typing allows objects to be treated as the required type based on their behavior, irrespective of their explicit type, as demonstrated in process_data's dynamic handling of objects with a quack method.
Find elsewhere
๐ŸŒ
Gui Commits
guicommits.com โ€บ python-generic-type-function-class
Python Generic function and class types - Gui Commits
March 1, 2024 - Typescript relies consistently on <> e.g. <int> and <str>. I do believe Python should follow the same logic for []. To make this happen we must define a class and override its __new__ magic method to behave like a function: import typing as t T = t.TypeVar("T") # ๐Ÿ‘‡ Keep class name lowercase so it feels like a function, name it as you want your '''function''' to be named: class get_something(t.Generic[T]): # ๐Ÿ‘‡ This is the secret def __new__( cls, v: str, # Add here as many args you think your function should take ): generated_instance = super().__new__(cls) return generated_instance.execute(v) # ๐Ÿ‘‡ Pretend this is your actual function implementation, name it anything you wish def execute(self, v: str) -> T: # ๐Ÿ‘ˆ Define T as the return type ...
๐ŸŒ
Real Python
realpython.com โ€บ ref โ€บ glossary โ€บ generic-type
generic type | Python Glossary โ€“ Real Python
The type parameter T in the class header makes Stack a generic class, and you use list[T] to store items of that type. The Stack supports pushing items of type T onto the stack and popping them off, enforcing type safety.
๐ŸŒ
Python.org
discuss.python.org โ€บ typing
Simplifying Parametrized Type Hints for Generic Functions in Python - Typing - Discussions on Python.org
July 4, 2024 - Hi everyone, Iโ€™m looking for a way to simplify the type hints for a generic function. Specifically, I want to parametrize a type of a function, where this type itself is also parametrized, and I want to specify the lattโ€ฆ
Top answer
1 of 2
43

Type variables are literally "variables for types". Similar to how regular variables allow code to apply to multiple values, type variables allow code to apply to multiple types.
At the same time, just like code is not required to apply to multiple values, it is not required to depend on multiple types. A literal value can be used instead of variables, and a literal type can be used instead of type variables โ€“ provided these are the only values/types applicable.

Since the Python language semantically only knows values โ€“ runtime types are also values โ€“ it does not have the facilities to express type variability. Namely, it cannot define, reference or scope type variables. Thus, typing represents these two concepts via concrete things:

  • A typing.TypeVar represents the definition and reference to a type variable.
  • A typing.Generic represents the scoping of types, specifically to class scope.

Notably, it is possible to use TypeVar without Generic โ€“ functions are naturally scoped โ€“ and Generic without TypeVar โ€“ scopes may use literal types.


Consider a function to add two things. The most naive implementation adds two literal things:

def add():
    return 5 + 12

That is valid but needlessly restricted. One would like to parameterise the two things to add โ€“ this is what regular variables are used for:

def add(a, b):
    return a + b

Now consider a function to add two typed things. The most naive implementations adds two things of literal type:

def add(a: int, b: int) -> int:
    return a + b

That is valid but needlessly restricted. One would like to parameterise the types of the two things to add โ€“ this is what type variables are used for:

T = TypeVar("T")

def add(a: T, b: T) -> T:
    return a + b

Now, in the case of values we defined two variables โ€“ a and b but in the case of types we defined one variable โ€“ the single T โ€“ but used for both variables! Just like the expression a + a would mean both operands are the same value, the annotation a: T, b: T means both parameters are the same type. This is because our function has a strong relation between the types but not the values.


While type variables are automatically scoped in functions โ€“ to the function scope โ€“ this is not the case for classes: a type variable might be scoped across all methods/attributes of a class or specific to some method/attribute.

When we define a class, we may scope type variables to the class scope by adding them as parameters to the class. Notably, parameters are always variables โ€“ this applies to regular parameters just as for type parameters. It just does not make sense to parameterise a literal.

#       v value parameters of the function are "value variables"
def mapping(keys, values):
    ...

#       v type parameters of the class are "type variables"
class Mapping(Generic[KT, VT]):
    ...

When we use a class, the scope of its parameters has already been defined. Notably, the arguments passed in may be literal or variable โ€“ this again applies to regular arguments just as for type arguments.

#       v pass in arguments via literals
mapping([0, 1, 2, 3], ['zero', 'one', 'two', 'three'])
#       v pass in arguments via variables
mapping(ks, vs)

#          v pass in arguments via literals
m: Mapping[int, str]
#          v pass in arguments via variables
m: Mapping[KT, VT]

Whether to use literals or variables and whether to scope them or not depends on the use-case. But we are free to do either as required.

2 of 2
6

The whole purporse of using Generic and TypeVar (here represented as the X and Y variables) is when one wants the parameters to be as generic as possible. int can be used, instead, in this case. The difference is: the static analyzer will interpret the parameter as always being an int.

Using generics mean the function accepts any type of parameter. The static analyzer, as in an IDE for instance, will determine the type of the variables and the return type as the arguments are provided on function call or object instantiation.


mapping: Mapping[str, int] = {"2": 2, "3": 3}
name = lookup_name(mapping, "1", 1)

In the above example type checkers will know name will always be an int relying on the type annotations. In IDEs, code completion for int methods will be shown as the 'name' variable is used.

Using specific types is ideal if that is your goal. The function accepting only a map with int keys or values, and/or returning int in this case, for instance.

As X and Y are variable you can choose any name want, basically.

Below example is possible:

def lookup_name(mapping: Mapping[str, int], key: str, default: int) -> int:
    try:
        return mapping[key]
    except KeyError:
        return default

The types are not generic in the above example. The key will always be str; the default variable, the value, and the return type will always be an int. It's the programmer's choice. This is not enforced by Python, though. A static type checker like mypy is needed for that.

The Generic type could even be constrained if wanted:

import typing

X = typing.TypeVar("X", int, str) # Accept int and str
Y = typing.TypeVar("Y", int) # Accept only int

@MisterMiyagi's answer offers a thorough explanation on the use scope for TypeVar and Generic.

๐ŸŒ
Tutorialspoint
tutorialspoint.com โ€บ python โ€บ python_generics.htm
Python - Generics
Here, we define a generic function called 'reverse'. The function takes a list ('List[T]') as an argument and returns a list of the same type.
๐ŸŒ
ArjanCodes
arjancodes.com โ€บ blog โ€บ python-generics-syntax
Python 3.12 Generics: Cleaner, Easier Type-Hinting | ArjanCodes
June 6, 2024 - A generic is a type of object whose behavior doesnโ€™t depend on the type it is handling and instead can be used in the same way for many types. An example of a generic is the built-in list[T] object, where T is the generic type.
Top answer
1 of 5
102

Python >= 3.8

As of Python3.8 there is typing.get_args:

print( get_args( List[int] ) ) # (<class 'int'>,)

PEP-560 also provides __orig_bases__[n], which allows us the arguments of the nth generic base:

from typing import TypeVar, Generic, get_args

T = TypeVar( "T" )

class Base( Generic[T] ):
    pass

class Derived( Base[int] ):
    pass

print( get_args( Derived.__orig_bases__[0] ) ) # (<class 'int'>,)

Python >= 3.6

As of Python 3.6. there is a public __args__ and (__parameters__) field. For instance:

print( typing.List[int].__args__ )

This contains the generic parameters (i.e. int), whilst __parameters__ contains the generic itself (i.e. ~T).


Python < 3.6

Use typing_inspect.getargs


Some considerations

typing follows PEP8. Both PEP8 and typing are coauthored by Guido van Rossum. A double leading and trailing underscore is defined in as: "โ€œmagicโ€ objects or attributes that live in user-controlled namespaces".

The dunders are also commented in-line; from the official repository for typing we can see:

  • "__args__ is a tuple of all arguments used in subscripting, e.g., Dict[T, int].__args__ == (T, int)".

However, the authors also note:

  • "The typing module has provisional status, so it is not covered by the high standards of backward compatibility (although we try to keep it as much as possible), this is especially true for (yet undocumented) dunder attributes like __union_params__. If you want to work with typing types in runtime context, then you may be interested in the typing_inspect project (part of which may end up in typing later)."

I general, whatever you do with typing will need to be kept up-to-date for the time being. If you need forward compatible changes, I'd recommend writing your own annotation classes.

2 of 5
14

As far as I know, there is no happy answer here.

What comes to mind is the __args__ undocumented attribute which stores this information:

list_of_ints.__args__
>>>(<class 'int'>,)

str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)

but there is no mention of it in the documentation of the typing module.

It is worth noting that it was very close to be mentioned in the documentation though:

Probably we should also discuss whether we need to document all keyword arguments for GenericMeta.__new__. There are tvars, args, origin, extra, and orig_bases. I think we could say something about first three (they correspond to __parameters__, __args__, and __origin__ and these are used by most things in typing).

But it did not quite make it:

I added GenericMeta to __all__ and added docstrings to GenericMeta and GenericMeta.__new__ following the discussion in the issue. I decided not to describe __origin__ and friends in docstrings. Instead, I just added a comment at the place where they are first used.

From there, you still have three non-mutually exclusive options:

  • wait for the typing module to reach full maturity and hope these features will be documented soon

  • join the Python ideas mailing list and see if enough support can be gathered to make these internals public / part of the API

  • work in the meantime with the undocumented internals, making a gamble that there won't be changes to these or that the changes will be minor.

Note that the third point can hardly be avoided as even the API can be subject to changes:

The typing module has been included in the standard library on a provisional basis. New features might be added and API may change even between minor releases if deemed necessary by the core developers.

๐ŸŒ
Reddit
reddit.com โ€บ r/learnpython โ€บ when to make classes & functions generic in python?
r/learnpython on Reddit: When to make classes & functions generic in python?
August 29, 2023 -

I have been generally making my methods generic mostly for the following reasons:

  • I am subclassing an abstract class and want to override a method and narrow the type hinting in the arguments, which would otherwise violate the Liskov substitution principle

  • I am not subclassing/overriding, but would like return values, attributes, etc. of a class to be more narrow than the type hints it is currently bound to, since I may be using that class in many different places with different types.

In particular for the second case, I have realized that there are actually two approaches to tackle this:

  1. make the class generic and provide the type arguments for the instance type hints.

  2. do not make the class generic, but subclass simply for the purpose of updating the type hints

With projects I am working on, with "context" and "manager" classes, there can be many different attributes and methods with many different types (5+), hence, making the class generic on all of them is too verbose. If I do make a class generic, if any other attributes (containing instances of other classes) return those same generic types, I have to propagate the generic type down the entire chain when I am using composition instead of inheritance. This is something I would like to avoid. If I choose option 2, there would be an explosion of subclasses just to override the type hints.

When should I choose 1 or 2? Is there a better way to do this?

Option 1 example:

from typing import Self, TypeVar, Generic

BazT = TypeVar('BazT')

class Bar(Generic[BazT]):
    def method1(self: Self) -> BazT:
        ...

class Foo(Generic[BazT]):
    bar: Bar[BazT]
    
    def method1(self: Self, baz: BazT) -> None:
        ...
    
    def method2(self: Self) -> Bar[BazT]:
        ...

Option 2 example:

from typing import Self, Any, TypeVar

Baz = Any

class Bar:
    def method1(self: Self) -> Baz:
        ...

class Foo:
    bar: Bar
    
    def method1(self: Self, baz: Baz) -> None:
        ...
    
    def method2(self: Self) -> Bar:
        ...
        
BazNarrowed = TypeVar('BazNarrowed', bound=Baz) # (doesn't matter what this is just some more narrow type)

class BarSubclass(Bar):
    def method1(self: Self) -> BazNarrowed:
        ...

class FooSubclass(Foo):
    bar: BarSubclass
    
    def method1(self: Self, baz: BazNarrowed) -> None:
        ...
    
    def method2(self: Self) -> BarSubclass:
        ...
Top answer
1 of 3
2
I don't understand the, idunno, premise of the question, I guess? Firstly, narrowing the type of a function parameter (or more generally, anything that's contravariant) is not allowed. This is a violation of Liskov: class Parent: def func(self, x: int): ... class Child(Parent): def func(self, x: bool): ... And secondly, your 2nd example doesn't typecheck. The way you're using the TypeVar makes no sense - it's neither bound to a generic class, nor does it appear in the function signature more than once. It's really unclear to me what problem you're trying to solve.
2 of 3
1
Please roast me if I'm wrong on this, but this appears to be the exact situation for which TypeGuard in typing module was developed, as of Python 3.10. PEP-647 ~~~TypeGuard = typing.TypeGuard~~~ Special typing form used to annotate the return type of a user-defined type guard function. ``TypeGuard`` only accepts a single type argument. At runtime, functions marked this way should return a boolean. ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static type checkers to determine a more precise type of an expression within a program's code flow. Usually type narrowing is done by analyzing conditional code flow and applying the narrowing to a block of code. The conditional expression here is sometimes referred to as a "type guard". Sometimes it would be convenient to use a user-defined boolean function as a type guard. Such a function should use ``TypeGuard[...]`` as its return type to alert static type checkers to this intention. Using ``-> TypeGuard`` tells the static type checker that for a given function: 1. The return value is a boolean. 2. If the return value is ``True``, the type of its argument is the type inside ``TypeGuard``. For example:: def is_str(val: Union[str, float]): # "isinstance" type guard if isinstance(val, str): # Type of ``val`` is narrowed to ``str`` ... else: # Else, type of ``val`` is narrowed to ``float``. ... Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower form of ``TypeA`` (it can even be a wider form) and this may lead to type-unsafe results. The main reason is to allow for things like narrowing ``List[object]`` to ``List[str]`` even though the latter is not a subtype of the former, since ``List`` is invariant. The responsibility of writing type-safe type guards is left to the user. ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards).