Skip to content

Commit

Permalink
Make cache-max-size-none check functools.cache
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielNoord committed Apr 5, 2022
1 parent bab3cb6 commit 3d19cd0
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 16 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Expand Up @@ -40,6 +40,10 @@ Release date: TBA

Closes #5936

* * Added ``cache-max-size-none`` checker will now also check ``functools.cache``.

Closes #5670

* ``potential-index-error``: Emitted when the index of a list or tuple exceeds its length.
This checker is currently quite conservative to avoid false positives. We welcome
suggestions for improvements.
Expand Down
9 changes: 9 additions & 0 deletions doc/data/messages/c/cache-max-size-none/bad.py
@@ -0,0 +1,9 @@
import functools


class Fibonnaci:
@functools.cache # [cache-max-size-none]
def fibonacci(self, n):
if n in {0, 1}:
return n
return self.fibonacci(n - 1) + self.fibonacci(n - 2)
13 changes: 13 additions & 0 deletions doc/data/messages/c/cache-max-size-none/good.py
@@ -0,0 +1,13 @@
import functools


@functools.cache
def cached_fibonacci(n):
if n in {0, 1}:
return n
return cached_fibonacci(n - 1) + cached_fibonacci(n - 2)


class Fibonnaci:
def fibonacci(self, n):
return cached_fibonacci(n)
4 changes: 4 additions & 0 deletions doc/whatsnew/2.14.rst
Expand Up @@ -71,6 +71,10 @@ Other Changes

* The concept of checker priority has been removed.

* Added ``cache-max-size-none`` checker will now also check ``functools.cache``.

Closes #5670

* The ``set_config_directly`` decorator has been removed.

* The ``ignore-mixin-members`` option has been deprecated. You should now use the new
Expand Down
22 changes: 13 additions & 9 deletions pylint/checkers/stdlib.py
Expand Up @@ -429,10 +429,10 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
"from code that is not actively being debugged.",
),
"W1517": (
"'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self'",
"'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self'",
"cache-max-size-none",
"By decorating a method with lru_cache the 'self' argument will be linked to "
"the lru_cache function and therefore never garbage collected. Unless your instance "
"By decorating a method with lru_cache or cache the 'self' argument will be linked to "
"the function and therefore never garbage collected. Unless your instance "
"will never need to be garbage collected (singleton) it is recommended to refactor "
"code to avoid this pattern or add a maxsize to the cache."
"The default value for maxsize is 128.",
Expand Down Expand Up @@ -575,23 +575,27 @@ def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None:
try:
for infered_node in d_node.infer():
q_name = infered_node.qname()
if q_name in NON_INSTANCE_METHODS or q_name not in LRU_CACHE:
if q_name in NON_INSTANCE_METHODS:
return

# Check if there is a maxsize argument set to None in the call
if isinstance(d_node, nodes.Call):
if q_name in LRU_CACHE and isinstance(d_node, nodes.Call):
try:
arg = utils.get_argument_from_call(
d_node, position=0, keyword="maxsize"
)
except utils.NoSuchArgumentError:
return
break

if not isinstance(arg, nodes.Const) or arg.value is not None:
return
break

lru_cache_nodes.append(d_node)
break
lru_cache_nodes.append(d_node)
break

if q_name == "functools.cache":
lru_cache_nodes.append(d_node)
break
except astroid.InferenceError:
pass
for lru_cache_node in lru_cache_nodes:
Expand Down
14 changes: 7 additions & 7 deletions tests/functional/c/cache_max_size_none.txt
@@ -1,7 +1,7 @@
cache-max-size-none:25:5:25:20:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:29:5:29:30:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:33:5:33:38:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:37:5:37:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:42:5:42:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:43:5:43:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:73:5:73:40:MyClassWithMethodsAndMaxSize.my_func:'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:25:5:25:20:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:29:5:29:30:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:33:5:33:38:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:37:5:37:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:42:5:42:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:43:5:43:24:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:73:5:73:40:MyClassWithMethodsAndMaxSize.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
47 changes: 47 additions & 0 deletions tests/functional/c/cache_max_size_none_py39.py
@@ -0,0 +1,47 @@
"""Tests for cache-max-size-none"""
# pylint: disable=no-self-use, missing-function-docstring, reimported, too-few-public-methods
# pylint: disable=missing-class-docstring, function-redefined

import functools
import functools as aliased_functools
from functools import cache
from functools import cache as aliased_cache


@cache
def my_func(param):
return param + 1


class MyClassWithMethods:
@cache
@staticmethod
def my_func(param):
return param + 1

@cache
@classmethod
def my_func(cls, param):
return param + 1

@cache # [cache-max-size-none]
def my_func(self, param):
return param + 1

@functools.cache # [cache-max-size-none]
def my_func(self, param):
return param + 1

@aliased_functools.cache # [cache-max-size-none]
def my_func(self, param):
return param + 1

@aliased_cache # [cache-max-size-none]
def my_func(self, param):
return param + 1

# Check double decorating to check robustness of checker itself
@functools.lru_cache(maxsize=1)
@aliased_cache # [cache-max-size-none]
def my_func(self, param):
return param + 1
2 changes: 2 additions & 0 deletions tests/functional/c/cache_max_size_none_py39.rc
@@ -0,0 +1,2 @@
[testoptions]
min_pyver = 3.9
5 changes: 5 additions & 0 deletions tests/functional/c/cache_max_size_none_py39.txt
@@ -0,0 +1,5 @@
cache-max-size-none:27:5:27:10:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:31:5:31:20:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:35:5:35:28:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:39:5:39:18:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE
cache-max-size-none:45:5:45:18:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE

0 comments on commit 3d19cd0

Please sign in to comment.