Search code examples
pythongenericspython-decoratorsmypy

How to type function returning a class decorator? (python3.11)


I know I can type a class decorator as follows:

T = TypeVar('T', bound=Type[Any])
def decorator(cls: T) -> T:
    ...

Which can be used:

@decorator
class Foo:
   foo: int

And a reference to foo on instance of Foo will check properly in mypy.

But how do I type a function that returns a decorator, so that the decorated class types correctly? E.g. how can I fix the code below, if the arguments to decorator have types not associated with the type of the wrapped class?

T = TypeVar('T', bound=Type[Any])
Decorator: TypeAlias = Callable[[T], T]

def decorator(...) -> Decorator[Any]:

    def wrapper(cls: T) -> T:
       # some code modifying cls depending on args to "decorator"
       ...
       return cls
    
    return wrapper

# now instance don't type write
@decorator(...)
class Foo:
    foo: int
    

Solution

  • Once the wrapper types become complex enough, I find that using typing.Protocol can be pretty useful. The function returned can be represented as non-generic class which supplies a generic __call__ method.

    import typing as t
    
    T = t.TypeVar('T', bound=t.Type[t.Any])
    
    
    class Decorator(t.Protocol):
        def __call__(self, cls: T) -> T:
            ...
    
    
    def decorator() -> Decorator:
        def wrapper(cls: T) -> T:
            # some code modifying cls depending on args to "decorator"
            ...
            return cls
    
        return wrapper
    
    
    @decorator()
    class Foo:
        foo: int
    
    
    t.reveal_type(Foo)
    t.assert_type(Foo(), Foo)
    
    (venv) > mypy --strict main.py
    main.py:25: note: Revealed type is "def () -> main.Foo"
    Success: no issues found in 1 source file