Skip to content

Commit 9d303cb

Browse files
kyoto7250github-actions[bot]
authored andcommitted
Ignore cached_property in method-hidden check (#8758)
Closes #8753 (cherry picked from commit e33920f)
1 parent a51dc64 commit 9d303cb

File tree

6 files changed

+61
-4
lines changed

6 files changed

+61
-4
lines changed

doc/whatsnew/fragments/8753.bugfix

+3
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

+24-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
_AccessNodes = Union[nodes.Attribute, nodes.AssignAttr]
5656

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

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

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

tests/functional/m/method_hidden.py

+13
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

+3-3
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
+16
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
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[testoptions]
2+
min_pyver = 3.9

0 commit comments

Comments
 (0)