summaryrefslogtreecommitdiff
path: root/pylint/checkers/stdlib.py
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2022-01-14 09:37:48 +0100
committerGitHub <noreply@github.com>2022-01-14 09:37:48 +0100
commit4a6b6bf33053c5887274da14e00dd22a7dcb4284 (patch)
tree3e059fc3eba90aa0b3099c7e2b0b3e64221910b3 /pylint/checkers/stdlib.py
parentfaf0c849fd3c8da5ed8fd46e80a024ce4b668073 (diff)
downloadpylint-git-4a6b6bf33053c5887274da14e00dd22a7dcb4284.tar.gz
Add ``lru-cache-decorating-method`` checker (#5674)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Diffstat (limited to 'pylint/checkers/stdlib.py')
-rw-r--r--pylint/checkers/stdlib.py43
1 files changed, 42 insertions, 1 deletions
diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py
index 638b7f3c9..a203f2d07 100644
--- a/pylint/checkers/stdlib.py
+++ b/pylint/checkers/stdlib.py
@@ -39,11 +39,12 @@
import sys
from collections.abc import Iterable
-from typing import TYPE_CHECKING, Any, Dict, Optional, Set
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set
import astroid
from astroid import nodes
+from pylint import interfaces
from pylint.checkers import BaseChecker, DeprecatedMixin, utils
from pylint.interfaces import IAstroidChecker
@@ -61,6 +62,12 @@ SUBPROCESS_POPEN = "subprocess.Popen"
SUBPROCESS_RUN = "subprocess.run"
OPEN_MODULE = {"_io", "pathlib"}
DEBUG_BREAKPOINTS = ("builtins.breakpoint", "sys.breakpointhook", "pdb.set_trace")
+LRU_CACHE = {
+ "functools.lru_cache", # Inferred for @lru_cache
+ "functools._lru_cache_wrapper.wrapper", # Inferred for @lru_cache() on >= Python 3.8
+ "functools.lru_cache.decorating_function", # Inferred for @lru_cache() on <= Python 3.7
+}
+NON_INSTANCE_METHODS = {"builtins.staticmethod", "builtins.classmethod"}
DEPRECATED_MODULES = {
@@ -446,6 +453,14 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
"Calls to breakpoint(), sys.breakpointhook() and pdb.set_trace() should be removed "
"from code that is not actively being debugged.",
),
+ "W1516": (
+ "lru_cache shouldn't be used on a method as it creates memory leaks",
+ "lru-cache-decorating-method",
+ "By decorating a method with lru_cache the 'self' argument will be linked to "
+ "to the lru_cache 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.",
+ ),
}
def __init__(
@@ -571,6 +586,32 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
for value in node.values:
self._check_datetime(value)
+ @utils.check_messages("lru-cache-decorating-method")
+ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
+ if node.decorators and isinstance(node.parent, nodes.ClassDef):
+ self._check_lru_cache_decorators(node.decorators)
+
+ def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None:
+ """Check if instance methods are decorated with functools.lru_cache."""
+ lru_cache_nodes: List[nodes.NodeNG] = []
+ for d_node in decorators.nodes:
+ try:
+ for infered_node in d_node.infer():
+ q_name = infered_node.qname()
+ if q_name in NON_INSTANCE_METHODS:
+ return
+ if q_name in LRU_CACHE:
+ lru_cache_nodes.append(d_node)
+ break
+ except astroid.InferenceError:
+ pass
+ for lru_cache_node in lru_cache_nodes:
+ self.add_message(
+ "lru-cache-decorating-method",
+ node=lru_cache_node,
+ confidence=interfaces.INFERENCE,
+ )
+
def _check_redundant_assert(self, node, infer):
if (
isinstance(infer, astroid.BoundMethod)