Skip to content

Commit e33920f

Browse files
authored
Ignore cached_property in method-hidden check (#8758)
Closes #8753
1 parent e141906 commit e33920f

File tree

6 files changed

+61
-4
lines changed

6 files changed

+61
-4
lines changed

doc/whatsnew/fragments/8753.bugfix

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a false positive for ``method-hidden`` when using ``cached_property`` decorator.
2+
3+
Closes #8753

pylint/checkers/classes/class_checker.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
_AccessNodes = Union[nodes.Attribute, nodes.AssignAttr]
5050

5151
INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"}
52+
ALLOWED_PROPERTIES = {"bultins.property", "functools.cached_property"}
5253
BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"}
5354
ASTROID_TYPE_COMPARATORS = {
5455
nodes.Const: lambda a, b: a.value == b.value,
@@ -1252,11 +1253,15 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
12521253
# attribute affectation will call this method, not hiding it
12531254
return
12541255
if isinstance(decorator, nodes.Name):
1255-
if decorator.name == "property":
1256+
if decorator.name in ALLOWED_PROPERTIES:
12561257
# attribute affectation will either call a setter or raise
12571258
# an attribute error, anyway not hiding the function
12581259
return
12591260

1261+
if isinstance(decorator, nodes.Attribute):
1262+
if self._check_functools_or_not(decorator):
1263+
return
1264+
12601265
# Infer the decorator and see if it returns something useful
12611266
inferred = safe_infer(decorator)
12621267
if not inferred:
@@ -1454,6 +1459,24 @@ def _check_invalid_overridden_method(
14541459
node=function_node,
14551460
)
14561461

1462+
def _check_functools_or_not(self, decorator: nodes.Attribute) -> bool:
1463+
if decorator.attrname != "cached_property":
1464+
return False
1465+
1466+
if not isinstance(decorator.expr, nodes.Name):
1467+
return False
1468+
1469+
_, import_nodes = decorator.expr.lookup(decorator.expr.name)
1470+
1471+
if not import_nodes:
1472+
return False
1473+
import_node = import_nodes[0]
1474+
1475+
if not isinstance(import_node, (astroid.Import, astroid.ImportFrom)):
1476+
return False
1477+
1478+
return "functools" in dict(import_node.names)
1479+
14571480
def _check_slots(self, node: nodes.ClassDef) -> None:
14581481
if "__slots__" not in node.locals:
14591482
return

tests/functional/m/method_hidden.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# pylint: disable=unused-private-member
33
"""check method hiding ancestor attribute
44
"""
5+
import functools as ft
6+
import something_else as functools # pylint: disable=import-error
57

68

79
class Abcd:
@@ -106,13 +108,24 @@ def default(self, o):
106108
class Parent:
107109
def __init__(self):
108110
self._protected = None
111+
self._protected_two = None
109112

110113

111114
class Child(Parent):
112115
def _protected(self): # [method-hidden]
113116
pass
114117

115118

119+
class CachedChild(Parent):
120+
@ft.cached_property
121+
def _protected(self):
122+
pass
123+
124+
@functools.cached_property
125+
def _protected_two(self):
126+
pass
127+
128+
116129
class ParentTwo:
117130
def __init__(self):
118131
self.__private = None

tests/functional/m/method_hidden.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
method-hidden:17:4:17:12:Cdef.abcd:An attribute defined in functional.m.method_hidden line 11 hides this method:UNDEFINED
2-
method-hidden:85:4:85:11:One.one:An attribute defined in functional.m.method_hidden line 83 hides this method:UNDEFINED
3-
method-hidden:112:4:112:18:Child._protected:An attribute defined in functional.m.method_hidden line 108 hides this method:UNDEFINED
1+
method-hidden:19:4:19:12:Cdef.abcd:An attribute defined in functional.m.method_hidden line 13 hides this method:UNDEFINED
2+
method-hidden:87:4:87:11:One.one:An attribute defined in functional.m.method_hidden line 85 hides this method:UNDEFINED
3+
method-hidden:115:4:115:18:Child._protected:An attribute defined in functional.m.method_hidden line 110 hides this method:UNDEFINED
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# pylint: disable=too-few-public-methods,missing-docstring
2+
"""check method hiding ancestor attribute
3+
"""
4+
import something_else as functools # pylint: disable=import-error
5+
6+
7+
class Parent:
8+
def __init__(self):
9+
self._protected = None
10+
11+
12+
class Child(Parent):
13+
@functools().cached_property
14+
def _protected(self):
15+
# This test case is only valid for python3.9 and above
16+
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[testoptions]
2+
min_pyver = 3.9

0 commit comments

Comments
 (0)