diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2022-01-14 09:37:48 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-14 09:37:48 +0100 |
commit | 4a6b6bf33053c5887274da14e00dd22a7dcb4284 (patch) | |
tree | 3e059fc3eba90aa0b3099c7e2b0b3e64221910b3 /pylint/checkers/stdlib.py | |
parent | faf0c849fd3c8da5ed8fd46e80a024ce4b668073 (diff) | |
download | pylint-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.py | 43 |
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) |