Skip to content

lru_cache annotation doesn't work as a property #5858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
takeda opened this issue Oct 30, 2018 · 7 comments · Fixed by #15926
Closed

lru_cache annotation doesn't work as a property #5858

takeda opened this issue Oct 30, 2018 · 7 comments · Fixed by #15926
Labels
bug mypy got something wrong topic-descriptors Properties, class vs. instance attributes

Comments

@takeda
Copy link

takeda commented Oct 30, 2018

So I originally reported it in typeshed/#2659 but was told this can't be fixed there and it is mypy limitation so repporting it here.


I have following code:

#! /usr/bin/env python

import time
from functools import lru_cache

class MyClass:
	@property
	@lru_cache(1)
	def expensive_operation(self) -> int:
		time.sleep(2)
		return 42

	def another_operation(self) -> int:
		return self.expensive_operation + 1

if __name__ == '__main__':
	o = MyClass()
	print(o.expensive_operation)
	print(o.expensive_operation)
	print(o.another_operation())

It works as expected:

$ ./test.py
42
42
43

Yet, mypy is showing errors:

$ mypy test.py
test.py:7: error: Decorated property not supported
test.py:14: error: Unsupported operand types for + ("_lru_cache_wrapper[int]" and "int")

The lru_cache works just fine when decorated as property, and the returned type should be int and not _lru_cache_wrapper[int].

This is on python 3.7.0 and mypy 0.641.

@takeda takeda changed the title lru_cache annotation doesn't work as property lru_cache annotation doesn't work as a property Oct 30, 2018
@probablyodd
Copy link

Duplicates #1362 and #1608

@sashgorokhov
Copy link

sashgorokhov commented Jan 14, 2019

@probablyodd well its a bit different. If you set # type: ignore to @property in this case,
mypy will completely ignore existence of this decorator and treat such methods as common callable methods. Mypy will just say somethingh like "lru_cache" has no attribute "..." if complex object such as dict returned from property method.

from functools import lru_cache

class Foo:
    @property  # type: ignore
    @lru_cache
    def bar(self) -> dict:
        return dict(key='value')

Foo().bar.get('key')
test.py:11: error: "lru_cache" has no attribute "get"

Edit:

Possible adhoc solution:

T = TypeVar('T')

def cache(func: Callable[..., T]) -> T:
    return functools.lru_cache()(func)  # type: ignore

...
    @property  # type: ignore
    @cache
    def foo(self) -> dict:
        return {}

@pgilad
Copy link

pgilad commented Jun 21, 2020

Besides @sashgorokhov awesome workaround (that adds some complexity to code) - is there any plan to support this ?

@malthunayan
Copy link

malthunayan commented Apr 29, 2021

Taking a look at the actual implementation of lru_cache and mypy's excellent documentation on decorator factories, here is another workaround for having type annotations work properly with functool's lru_cache:

import functools
from typing import Any, Callable, TypeVar, Union, no_type_check, overload


RT = TypeVar("RT", bound=Callable[..., Any])


@overload
def lru_cache(
    max_size: Callable[..., RT], typed: bool = False
) -> Callable[..., RT]:
    ...


@overload
def lru_cache(max_size: int, typed: bool = False) -> Callable[[RT], RT]:
    ...


@overload
def lru_cache(
    max_size: Union[Callable[..., RT], int], typed: bool = False
) -> Union[Callable[..., RT], Callable[[RT], RT]]:
    ...


@no_type_check
def lru_cache(*args, **kwargs):
    return functools.lru_cache(*args, **kwargs)

This maintains the signature of whatever callable you are decorating, and you should get proper type annotation checks. Credits goes to @sashgorokhov for inspiring to get this solution with his solution above.

@Doggie52
Copy link

Doggie52 commented Oct 2, 2021

Is this something that should be adjusted in mypy or is it technically the fault of lru_cache?

Also @malthunayan I can't seem to get your workaround to work with @lru_cache(maxsize=None).

@Doggie52
Copy link

Doggie52 commented Oct 2, 2021

Here's my modified workaround - not sure where your max_size is coming from, could have worked in previous Python versions? I'm on 3.8.

import functools
from typing import Any, Callable, TypeVar, Union, no_type_check, overload, Optional

RT = TypeVar("RT", bound=Callable[..., Any])


@overload
def lru_cache(
        maxsize: Callable[..., RT], typed: bool = False
) -> Callable[..., RT]:
    ...


@overload
def lru_cache(maxsize: Optional[int], typed: bool = False) -> Callable[[RT], RT]:
    ...


@overload
def lru_cache(
        maxsize: Union[Callable[..., RT], Optional[int]], typed: bool = False
) -> Union[Callable[..., RT], Callable[[RT], RT]]:
    ...


@no_type_check
def lru_cache(*args, **kwargs):
    return functools.lru_cache(*args, **kwargs)

@posita
Copy link
Contributor

posita commented Mar 19, 2022

If there is no plan to fix this soon, then can this be documented properly? This error is implicated by the recommendation in the standard library docs for functools.cached_property for caching properties in classes without proper __dict__s, but there's no hint that such recommendation would frustrate Mypy:

# test_5858.py
import statistics
from functools import cache

class DataSet:
    __slots__ = ("_data",)

    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @property  # <- error: Decorated property not supported  [misc]
    @cache  # <- results in downstream errors involving type "_lru_cache_wrapper[…]"
    def stdev(self):
        return statistics.stdev(self._data)

f: float = statistics.stdev(range(10))
f = DataSet(range(10)).stdev
% python --version
Python 3.9.10
% mypy --version
mypy 0.941
% mypy --config-file=/dev/null test_5858.py
/dev/null: No [mypy] section in config file
test_5858.py:11: error: Decorated property not supported
test_5858.py:17: error: Incompatible types in assignment (expression has type "_lru_cache_wrapper[Any]", variable has type "float")
Found 2 errors in 1 file (checked 1 source file)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-descriptors Properties, class vs. instance attributes
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants