Skip to content

Commit 1a276fc

Browse files
authored
Merge pull request #40 from pylint-dev/26-add-pandas-inplace-parameter-set-checker
26 add pandas inplace parameter set checker
2 parents cd643da + dbd42fc commit 1a276fc

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Licensed under the MIT: https://mit-license.org/
2+
# For details: https://github.com/pylint-dev/pylint-ml/LICENSE
3+
# Copyright (c) https://github.com/pylint-dev/pylint-ml/CONTRIBUTORS.txt
4+
5+
"""Check for improper usage of the inplace parameter in pandas operations."""
6+
7+
from __future__ import annotations
8+
9+
from astroid import nodes
10+
from pylint.checkers import BaseChecker
11+
from pylint.checkers.utils import only_required_for_messages
12+
from pylint.interfaces import HIGH
13+
14+
15+
class PandasInplaceChecker(BaseChecker):
16+
name = "pandas-inplace"
17+
msgs = {
18+
"W8109": (
19+
"Avoid using 'inplace=True' in pandas operations.",
20+
"pandas-inplace",
21+
"Using 'inplace=True' can lead to unclear and potentially problematic code. Prefer using assignment "
22+
"instead.",
23+
),
24+
}
25+
26+
_inplace_methods = {
27+
"drop",
28+
"fillna",
29+
"replace",
30+
"rename",
31+
"set_index",
32+
"reset_index",
33+
"sort_values",
34+
"sort_index",
35+
"drop_duplicates",
36+
"update",
37+
"clip",
38+
}
39+
40+
@only_required_for_messages("pandas-inplace")
41+
def visit_call(self, node: nodes.Call) -> None:
42+
# Check if the call is to a method that supports 'inplace'
43+
if isinstance(node.func, nodes.Attribute):
44+
method_name = node.func.attrname
45+
if method_name in self._inplace_methods:
46+
for keyword in node.keywords:
47+
if keyword.arg == "inplace" and getattr(keyword.value, "value", False) is True:
48+
self.add_message("pandas-inplace", node=node, confidence=HIGH)
49+
break
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import astroid
2+
import pylint.testutils
3+
from pylint.interfaces import HIGH
4+
5+
from pylint_ml.checkers.pandas.pandas_inplace import PandasInplaceChecker
6+
7+
8+
class TestPandasInplaceChecker(pylint.testutils.CheckerTestCase):
9+
CHECKER_CLASS = PandasInplaceChecker
10+
11+
def test_inplace_used_in_drop(self):
12+
node = astroid.extract_node(
13+
"""
14+
import pandas as pd
15+
df = pd.DataFrame({
16+
"A": [1, 2, 3],
17+
"B": [4, 5, 6]
18+
})
19+
df.drop(columns=["A"], inplace=True) # [pandas-inplace]
20+
"""
21+
)
22+
with self.assertAddsMessages(
23+
pylint.testutils.MessageTest(
24+
msg_id="pandas-inplace",
25+
confidence=HIGH,
26+
node=node,
27+
),
28+
ignore_position=True,
29+
):
30+
self.checker.visit_call(node)
31+
32+
def test_inplace_used_in_fillna(self):
33+
node = astroid.extract_node(
34+
"""
35+
import pandas as pd
36+
df = pd.DataFrame({
37+
"A": [1, None, 3],
38+
"B": [4, 5, None]
39+
})
40+
df.fillna(0, inplace=True) # [pandas-inplace]
41+
"""
42+
)
43+
with self.assertAddsMessages(
44+
pylint.testutils.MessageTest(
45+
msg_id="pandas-inplace",
46+
confidence=HIGH,
47+
node=node,
48+
),
49+
ignore_position=True,
50+
):
51+
self.checker.visit_call(node)
52+
53+
def test_inplace_used_in_sort_values(self):
54+
node = astroid.extract_node(
55+
"""
56+
import pandas as pd
57+
df = pd.DataFrame({
58+
"A": [3, 2, 1],
59+
"B": [4, 5, 6]
60+
})
61+
df.sort_values(by="A", inplace=True) # [pandas-inplace]
62+
"""
63+
)
64+
with self.assertAddsMessages(
65+
pylint.testutils.MessageTest(
66+
msg_id="pandas-inplace",
67+
confidence=HIGH,
68+
node=node,
69+
),
70+
ignore_position=True,
71+
):
72+
self.checker.visit_call(node)
73+
74+
def test_no_inplace(self):
75+
node = astroid.extract_node(
76+
"""
77+
import pandas as pd
78+
df = pd.DataFrame({
79+
"A": [1, 2, 3],
80+
"B": [4, 5, 6]
81+
})
82+
df = df.drop(columns=["A"]) # This should not trigger any warnings
83+
"""
84+
)
85+
86+
inplace_call = node.value
87+
88+
with self.assertNoMessages():
89+
self.checker.visit_call(inplace_call)
90+
91+
def test_inplace_used_in_unsupported_method(self):
92+
node = astroid.extract_node(
93+
"""
94+
import pandas as pd
95+
df = pd.DataFrame({
96+
"A": [1, 2, 3],
97+
"B": [4, 5, 6]
98+
})
99+
df.append({"A": 4, "B": 7}, inplace=True) # This should not trigger any warnings
100+
"""
101+
)
102+
103+
with self.assertNoMessages():
104+
self.checker.visit_call(node)

0 commit comments

Comments
 (0)