Search code examples
pythonpycharmpython-typing

PyCharm cannot infer @cache-d methods' return types


PyCharm's type checker works well with this:

from functools import cache

class MyType:
  pass

@cache
def f() -> MyType:
  ...

v = f()  # v: MyType

...but not this:

class C:

  @cache
  def m(self) -> MyType:
    ...

v = C().m()  # v: Any

What should I do to get good autocompletion?


Solution

  • This is YouTrack/PY-49635 (open as of November 2023).

    My current workaround is the following:

    from functools import cache
    from typing import TypeVar
    
    if TYPE_CHECKING:
      # To be read by mypy and other proper typecheckers
    
      _cache = cache
    
    else:
      # To be read by PyCharm
      # This is not actually type-safe since T is unbound, but whatever.
    
      T = TypeVar('T')
    
      def _cache(function: T) -> T:
        '''
        Workaround for `PY-49635\
        <https://youtrack.jetbrains.com/issue/PY-49635>`_.
            
        :param function: A callable.
        :return: The same callable, wrapped in :func:`functools.cache`.
        '''
            
        return cache(function)
    

    Anything in the else block is supposed to be ignored by typecheckers and always run by the interpreter. However, PyCharm doesn't honor that. In a way, that's good: it allows us to write code that doesn't cause any unnecessary warnings. Mypy/Pyright/Your-decent-typechecker is smart enough to turn a blind eye to our workaround function and PyCharm is lenient enough not to warn us.

    This is very fragile and may break in future PyCharm releases. That is, if the JetBrains guys ever "fix" this behaviour.

    Usage:

    @_cache
    def f() -> YourType:
      ...
    
    class C:
    
      @_cache
      def m(self) -> YourType:
        ...
    
    v = f()      # v: YourType
    v = C().m()  # v: YourType
    

    The workaround above works in PyCharm 2023.2.5. I haven't tested with other versions.