python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.5k stars 2.83k forks source link

mypy does not take into account decorators #12573

Open davekch opened 2 years ago

davekch commented 2 years ago

Bug Report

When decorating a function in a way that should resolve mypy errors, mypy still reports an error.

To Reproduce

This works just fine:

from typing import Optional, List

class Foo:
    def __init__(self):
        self.bar: Optional[str] = None

    def barsplit(self) -> List[str]:
        if not self.bar:
            raise ValueError
        return self.bar.split()

A pattern I often use in this case is to move the check if not self.bar: ... into a decorator:

from typing import List, Optional, Callable
from functools import wraps

def require_bar(method: Callable) -> Callable:
    @wraps(method)
    def decorated_method(self, *args, **kwargs):
        if not self.bar:
            raise ValueError
        return method(self, *args, **kwargs)

    return decorated_method

class Foo:
    def __init__(self):
        self.bar: Optional[str] = None

    @require_bar
    def get_barsplit(self) -> List[str]:
        return self.bar.split()

which is essentially the same thing, but here mypy complains: error: Item "None" of "Optional[str]" has no attribute "split"

Expected Behavior

mypy understands that the decorator makes sure that self.bar is not None. I also tried to make the type hints of the decorator more specific but it did not change the observed behaviour.

Actual Behavior

mypy gives the same error as if the decorator were not there.

Your Environment

JelleZijlstra commented 2 years ago

Mypy doesn't do cross-function type narrowing. This would be difficult to implement and would likely require a new type system feature.

davekch commented 2 years ago

I understand.

Is there a workaround other than self.bar: str = None # type: ignore, eg a flag/comment to ignore only this specific error only on methods that are decorated this way?

erictraut commented 2 years ago

If you want your code to work well with static type checking, I would recommend against using this technique. Static type checkers like mypy provide a way to detect and prevent bugs in your code at type checking time, but you need to work with the type checker, not against it. This pattern is working against it.