Skip to content

Commit 0741313

Browse files
DanielNoordjacobtylerwallsPierre-Sassoulas
committed
Only emit lru-cache-decorating-method when maxsize is None (#6181)
Co-authored-by: Jacob Walls <[email protected]> Co-authored-by: Pierre Sassoulas <[email protected]>
1 parent 7024743 commit 0741313

File tree

8 files changed

+109
-156
lines changed

8 files changed

+109
-156
lines changed

ChangeLog

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ Release date: TBA
2727
Closes #6069
2828
Closes #6136
2929

30+
* ``lru-cache-decorating-method`` has been renamed to ``cache-max-size-none`` and
31+
will only be emitted when ``maxsize`` is ``None``.
32+
33+
Closes #6180
34+
3035
* Fix false positive for ``unused-import`` when disabling both ``used-before-assignment`` and ``undefined-variable``.
3136

3237
Closes #6089

doc/whatsnew/2.13.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ New checkers
7878
enter on a non specialized keyboard.
7979
See `Confusable Characters in PEP 672 <https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers>`_
8080

81-
* Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache``
82-
on class methods. This is unrecommended as it creates memory leaks by never letting the instance
83-
getting garbage collected.
81+
* Added ``cache-max-size-none`` checker with checks for the use of ``functools.lru_cache``
82+
on class methods with a ``maxsize`` of ``None``. This is unrecommended as it
83+
creates memory leaks by never letting the instance get garbage collected.
8484

8585
Closes #5670
86+
Clsoes #6180
8687

8788
Removed checkers
8889
================

pylint/checkers/stdlib.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,15 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
428428
"Calls to breakpoint(), sys.breakpointhook() and pdb.set_trace() should be removed "
429429
"from code that is not actively being debugged.",
430430
),
431-
"W1516": (
432-
"'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self'",
433-
"lru-cache-decorating-method",
431+
"W1517": (
432+
"'lru_cache(maxsize=None)' will keep all method args alive indefinitely, including 'self'",
433+
"cache-max-size-none",
434434
"By decorating a method with lru_cache the 'self' argument will be linked to "
435435
"the lru_cache function and therefore never garbage collected. Unless your instance "
436436
"will never need to be garbage collected (singleton) it is recommended to refactor "
437-
"code to avoid this pattern or add a maxsize to the cache.",
437+
"code to avoid this pattern or add a maxsize to the cache."
438+
"The default value for maxsize is 128.",
439+
{"old_names": [("W1516", "lru-cache-decorating-method")]},
438440
),
439441
}
440442

@@ -561,7 +563,7 @@ def visit_boolop(self, node: nodes.BoolOp) -> None:
561563
for value in node.values:
562564
self._check_datetime(value)
563565

564-
@utils.check_messages("lru-cache-decorating-method")
566+
@utils.check_messages("cache-max-size-none")
565567
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
566568
if node.decorators and isinstance(node.parent, nodes.ClassDef):
567569
self._check_lru_cache_decorators(node.decorators)
@@ -573,28 +575,28 @@ def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None:
573575
try:
574576
for infered_node in d_node.infer():
575577
q_name = infered_node.qname()
576-
if q_name in NON_INSTANCE_METHODS:
577-
return
578-
if q_name not in LRU_CACHE:
578+
if q_name in NON_INSTANCE_METHODS or q_name not in LRU_CACHE:
579579
return
580580

581-
# Check if there is a maxsize argument to the call
581+
# Check if there is a maxsize argument set to None in the call
582582
if isinstance(d_node, nodes.Call):
583583
try:
584-
utils.get_argument_from_call(
584+
arg = utils.get_argument_from_call(
585585
d_node, position=0, keyword="maxsize"
586586
)
587-
return
588587
except utils.NoSuchArgumentError:
589-
pass
588+
return
589+
590+
if not isinstance(arg, nodes.Const) or arg.value is not None:
591+
return
590592

591593
lru_cache_nodes.append(d_node)
592594
break
593595
except astroid.InferenceError:
594596
pass
595597
for lru_cache_node in lru_cache_nodes:
596598
self.add_message(
597-
"lru-cache-decorating-method",
599+
"cache-max-size-none",
598600
node=lru_cache_node,
599601
confidence=interfaces.INFERENCE,
600602
)

pylint/message/message_definition_store.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ def register_message(self, message: MessageDefinition) -> None:
4848
self._messages_definitions[message.msgid] = message
4949
self._msgs_by_category[message.msgid[0]].append(message.msgid)
5050

51-
# We disable the message here because MessageDefinitionStore is only
52-
# initialized once and due to the size of the class does not run the
51+
# Since MessageDefinitionStore is only initialized once
52+
# and the arguments are relatively small in size we do not run the
5353
# risk of creating a large memory leak.
5454
# See discussion in: https://github.com/PyCQA/pylint/pull/5673
55-
@functools.lru_cache() # pylint: disable=lru-cache-decorating-method
55+
@functools.lru_cache(maxsize=None) # pylint: disable=cache-max-size-none
5656
def get_message_definitions(self, msgid_or_symbol: str) -> List[MessageDefinition]:
5757
"""Returns the Message definition for either a numeric or symbolic id.
5858
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Tests for cache-max-size-none"""
2+
# pylint: disable=no-self-use, missing-function-docstring, reimported, too-few-public-methods
3+
# pylint: disable=missing-class-docstring, function-redefined
4+
5+
import functools
6+
import functools as aliased_functools
7+
from functools import lru_cache
8+
from functools import lru_cache as aliased_cache
9+
10+
11+
@lru_cache
12+
def my_func(param):
13+
return param + 1
14+
15+
16+
class MyClassWithMethods:
17+
@lru_cache()
18+
def my_func(self, param):
19+
return param + 1
20+
21+
@lru_cache(1)
22+
def my_func(self, param):
23+
return param + 1
24+
25+
@lru_cache(None) # [cache-max-size-none]
26+
def my_func(self, param):
27+
return param + 1
28+
29+
@functools.lru_cache(None) # [cache-max-size-none]
30+
def my_func(self, param):
31+
return param + 1
32+
33+
@aliased_functools.lru_cache(None) # [cache-max-size-none]
34+
def my_func(self, param):
35+
return param + 1
36+
37+
@aliased_cache(None) # [cache-max-size-none]
38+
def my_func(self, param):
39+
return param + 1
40+
41+
# Check double decorating to check robustness of checker itself
42+
@aliased_cache(None) # [cache-max-size-none]
43+
@aliased_cache(None) # [cache-max-size-none]
44+
def my_func(self, param):
45+
return param + 1
46+
47+
48+
class MyClassWithMethodsAndMaxSize:
49+
@lru_cache(maxsize=1)
50+
def my_func(self, param):
51+
return param + 1
52+
53+
@lru_cache(maxsize=1)
54+
def my_func(self, param):
55+
return param + 1
56+
57+
@lru_cache(typed=True)
58+
def my_func(self, param):
59+
return param + 1
60+
61+
@lru_cache(typed=True)
62+
def my_func(self, param):
63+
return param + 1
64+
65+
@lru_cache(typed=True, maxsize=1)
66+
def my_func(self, param):
67+
return param + 1
68+
69+
@lru_cache(typed=True, maxsize=1)
70+
def my_func(self, param):
71+
return param + 1
72+
73+
@lru_cache(typed=True, maxsize=None) # [cache-max-size-none]
74+
def my_func(self, param):
75+
return param + 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
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
2+
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
3+
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
4+
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
5+
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
6+
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
7+
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

tests/functional/l/lru_cache_decorating_method.py

-125
This file was deleted.

tests/functional/l/lru_cache_decorating_method.txt

-12
This file was deleted.

0 commit comments

Comments
 (0)