Skip to content

Commit

Permalink
Implemented #323: Added new check B910 to suggest using Counter() ins…
Browse files Browse the repository at this point in the history
…tead of defaultdict(int) (#489)

Co-authored-by: djvdq <[email protected]>
  • Loading branch information
dejvidq and djvdq authored Aug 20, 2024
1 parent 33993a4 commit 5939910
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ on the first line and urls or paths that are on their own line::
"https://some-super-long-domain-name.com/with/some/very/long/paths"
)

**B910**: Use Counter() instead of defaultdict(int) to avoid excessive memory use as the default dict will record missing keys with the default value when accessed.


How to enable opinionated warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
15 changes: 15 additions & 0 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ def visit_Call(self, node) -> None:
self.check_for_b034(node)
self.check_for_b039(node)
self.check_for_b905(node)
self.check_for_b910(node)

# no need for copying, if used in nested calls it will be set to None
current_b040_caught_exception = self.b040_caught_exception
Expand Down Expand Up @@ -1706,6 +1707,16 @@ def check_for_b909(self, node: ast.For) -> None:
):
self.errors.append(B909(mutation.lineno, mutation.col_offset))

def check_for_b910(self, node: ast.Call) -> None:
if (
isinstance(node.func, ast.Name)
and node.func.id == "defaultdict"
and len(node.args) > 0
and isinstance(node.args[0], ast.Name)
and node.args[0].id == "int"
):
self.errors.append(B910(node.lineno, node.col_offset))


def compose_call_path(node):
if isinstance(node, ast.Attribute):
Expand Down Expand Up @@ -2380,6 +2391,9 @@ def visit_Lambda(self, node) -> None:
"B909 editing a loop's mutable iterable often leads to unexpected results/bugs"
)
)
B910 = Error(
message="B910 Use Counter() instead of defaultdict(int) to avoid excessive memory use"
)
B950 = Error(message="B950 line too long ({} > {} characters)")


Expand All @@ -2392,5 +2406,6 @@ def visit_Lambda(self, node) -> None:
"B906",
"B908",
"B909",
"B910",
"B950",
]
8 changes: 8 additions & 0 deletions tests/b910.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from collections import defaultdict

a = defaultdict(int)
b = defaultdict(float)
c = defaultdict(bool)
d = defaultdict(str)
e = defaultdict()
f = defaultdict(int)
12 changes: 12 additions & 0 deletions tests/test_bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
B907,
B908,
B909,
B910,
B950,
BugBearChecker,
BugBearVisitor,
Expand Down Expand Up @@ -1042,6 +1043,17 @@ def test_b909(self):
]
self.assertEqual(errors, self.errors(*expected))

def test_b910(self):
filename = Path(__file__).absolute().parent / "b910.py"
mock_options = Namespace(select=[], extend_select=["B910"])
bbc = BugBearChecker(filename=str(filename), options=mock_options)
errors = list(bbc.run())
expected = [
B910(3, 4),
B910(8, 4),
]
self.assertEqual(errors, self.errors(*expected))


class TestFuzz(unittest.TestCase):
from hypothesis import HealthCheck, given, settings
Expand Down

0 comments on commit 5939910

Please sign in to comment.