Git Product home page Git Product logo

perflint's Introduction

Hi there 👋 Welcome to my GitHub Workshop! I'm Anthony Shaw, Python Software Foundation Fellow, ASF member, and Python-author. I work at Microsoft on the Open-Source Advocacy team focused on Python.

Some projects I'm working on right now that you might find interesting:

The most recently published blog posts:

My blog is up on my website along with links to all my 🎙podcast interviews and 👨‍conference talks. I'm currently working on a book, so check that out too!

GitHub Stats

I'm also on Mastodon.

perflint's People

Contributors

adamantike avatar jenstroeger avatar pr0ps avatar sobolevn avatar tonybaloney avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

perflint's Issues

IndexError: pop from empty list

After seeing a tweet to Will McGugan, I thought I should try it on my project and got a traceback

> pylint friendly_traceback/ --load-plugins=perflint > pylint_result.txt
Traceback (most recent call last):
  File "C:\Users\Andre\AppData\Local\Programs\Python\Python310\lib\site-packages\pylint\utils\ast_walker.py", line 77, in walk
    callback(astroid)
  File "C:\Users\Andre\AppData\Local\Programs\Python\Python310\lib\site-packages\perflint\for_loop_checker.py", line 170, in leave_for
    self._leave_loop(node)
  File "C:\Users\Andre\AppData\Local\Programs\Python\Python310\lib\site-packages\perflint\for_loop_checker.py", line 179, in _leave_loop
    assigned_names = self._loop_assignments.pop()
IndexError: pop from empty list

perflint did give me some useful pointers about loop invariants that I plan to investigate. Thank you for making this plugin.

Constant expressions are incorrectly marked as invariant

def constant_expressions():
    for i in range(10):
        _ = (0, 1, 2) # [loop-invariant]
        _ = [0, 1, 2] # [loop-invariant]
        _ = {"a": 1, "b": 2} # [loop-invariant]

Compiles to

  2           0 LOAD_CONST               1 ((0, 1, 2))
              2 STORE_FAST               0 (_)

  3           4 LOAD_CONST               2 (0)
              6 LOAD_CONST               3 (1)
              8 LOAD_CONST               4 (2)
             10 BUILD_LIST               3
             12 STORE_FAST               0 (_)

  4          14 LOAD_CONST               3 (1)
             16 LOAD_CONST               4 (2)
             18 LOAD_CONST               5 (('a', 'b'))
             20 BUILD_CONST_KEY_MAP      2
             22 STORE_FAST               0 (_)
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE
  • LOAD_CONST is as-fast as LOAD_FAST
  • BUILD_LIST is as-fast as list.copy()
  • BUILD_CONST_KEY_MAP is probably faster than dict.copy()

loop-invariant-statement is duplicated and false positive

In the example below expression depends on the loop variable and cannot be moved out of the loop.

def foo():
    for f in ["v1", "v2"]:
        if f == "v1":
            obj.f1 = True
        elif f == "v2":
            obj.f2 = True
$ perflint test.py
************* Module test
test.py:4:12: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)
test.py:6:12: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)
test.py:4:12: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)
test.py:6:12: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)

Also warnings are repeated twice for now obvious reason.

RuntimeError using --jobs

MacOS, Python3.10

> perflint -j 4 code

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 125, in _main
    prepare(preparation_data)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 236, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 287, in _fixup_main_from_path
    main_content = runpy.run_path(main_path,
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/runpy.py", line 289, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/runpy.py", line 96, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/Users/admin/Documents/my_project/.venv/bin/perflint", line 5, in <module>
    from perflint import __main__
  File "/Users/admin/Documents/my_project/.venv/lib/python3.10/site-packages/perflint/__main__.py", line 26, in <module>
    PylintRun(args)
  File "/Users/admin/Documents/my_project/.venv/lib/python3.10/site-packages/pylint/lint/run.py", line 207, in __init__
    linter.check(args)
  File "/Users/admin/Documents/my_project/.venv/lib/python3.10/site-packages/pylint/lint/pylinter.py", line 666, in check
    check_parallel(
  File "/Users/admin/Documents/my_project/.venv/lib/python3.10/site-packages/pylint/lint/parallel.py", line 140, in check_parallel
    with multiprocessing.Pool(
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/context.py", line 119, in Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/pool.py", line 215, in __init__
    self._repopulate_pool()
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/pool.py", line 306, in _repopulate_pool
    return self._repopulate_pool_static(self._ctx, self.Process,
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/pool.py", line 329, in _repopulate_pool_static
    w.start()
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/context.py", line 288, in _Popen
    return Popen(process_obj)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/popen_spawn_posix.py", line 32, in __init__
    super().__init__(process_obj)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/popen_fork.py", line 19, in __init__
    self._launch(process_obj)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/popen_spawn_posix.py", line 42, in _launch
    prep_data = spawn.get_preparation_data(process_obj._name)
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 154, in get_preparation_data
    _check_not_importing_main()
  File "/Users/admin/.pyenv/versions/3.10.6/lib/python3.10/multiprocessing/spawn.py", line 134, in _check_not_importing_main
    raise RuntimeError('''
RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

`__all__` is erroneously flagged with W8301

Describe the bug

When pylinting my code perflint erroneously flags __all__ with W8301.

To reproduce

perflint.py

"""perflint test module."""

__all__ = ["dummy"]

def dummy() -> None:
    """Dummy docstring."""
    raise NotImplementedError

Run pylint:

$ pylint --load-plugin=perflint perflint.py
************* Module perflint
perflint.py:3:10: W8301: Use tuple instead of list for a non-mutated sequence (use-tuple-over-list)

------------------------------------------------------------------
Your code has been rated at 6.67/10 (previous run: 3.33/10, +3.33)

Expected behavior

__all__ should not be flagged.

While __all__ [...] must be a sequence of strings, and a tuple is a sequence, the standard is to declare __all__ as list[str].

Too many false positives

Great project! I found at least one line of code that could be improved (moved loop to a dictionary expression); but I see, however, 99.5% false positives, e.g.:

  • loop-try-except-usage can only be moved outside the loop, if the except clause contains a break. Otherwise I want to keep processing the loop
  • loop-invariant-statement is usually not at all invariant but depends on a loop variable
  • use-tuple-over-list is most often typed as a List[...] and therefore cannot and should not hold a tuple, even if initiated empty

(Enhancement) Add flake8 plugin

Hello, I use flake8 for linting my code, and there are flake8 plugins for a lot of things, like flake8-isort for isort, even flake8-pylint for pylint, but unfortunately flake8-pylint doesn't work with pylint plugins.
I have no idea how hard it is to port a pylint plugin to flake8, but if I'm able to, I would be glad to help.

W8401 (use-list-comprehension) with walrus operator

I'm not sure the W8401 should trigger when a walrus operator is implied in the condition.

The warning appear for this code.

def odd_only(a):
    if a % 2:
        return a
    return False

filtered = []
for a in range(1, 5):
    if b := odd_only(a):
        filtered.append(b)

Which should be changed to

filtered = [b for a in range(1, 5) if (b := odd_only(a))]

I'm found this kind of code not clear, because of the walrus operator.

The workaround is to not use the walrus operator:

filtered = []
for a in range(1, 5):
    b = odd_only(a)
    if b:
        filtered.append(b)

So, as there is an equivalence, and the warn is not triggered, it should also not warn when a walrus operator is used.

Heisenbug Error when using `--reports=yes` flag

Hi, we have an experimental CI build where we are testing this tool out, and recently I have noticed that it intermittently fails on our codebase with this stack trace:

Executing command: perflint $SRC_DIR --exit-zero --reports=yes > perflint.txt                         
Traceback (most recent call last):                                                                    
  File "/usr/local/bin/perflint", line 5, in <module>                                                 
    from perflint import __main__                                                                     
  File "/usr/local/lib/python3.10/site-packages/perflint/__main__.py", line 26, in <module>           
    PylintRun(args)                                                                                   
  File "/usr/local/lib/python3.10/site-packages/pylint/lint/run.py", line 214, in __init__            
    score_value = linter.generate_reports()                                                           
  File "/usr/local/lib/python3.10/site-packages/pylint/lint/pylinter.py", line 1149, in generate_reports                                                                                                                               
    sect = self.make_reports(self.stats, previous_stats)                                              
  File "/usr/local/lib/python3.10/site-packages/pylint/reporters/reports_handler_mix_in.py", line 76, in make_reports                                                                                                                  
    r_cb(report_sect, stats, old_stats)                                                               
  File "/usr/local/lib/python3.10/site-packages/pylint/checkers/imports.py", line 981, in _report_external_dependencies                                                                                                                
    dep_info = _make_tree_defs(self._external_dependencies_info().items())                            
  File "/usr/local/lib/python3.10/site-packages/pylint/checkers/imports.py", line 179, in _make_tree_defs                                                                                                                              
    node[1] += files                                                                                  
TypeError: 'tuple' object does not support item assignment 

The funny part is with no changes to our underlying code, if I re-trigger the same build on the same state of the code, it will usually pass. Happy to provide further information if I can.

loop-invariant-statement triggered by dict creation in `else` branch of `for-else`

The following incorrectly results in perflint_issue.py:6:15: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)

def findfirstkey(items, key):
    for item in items:
        if item["key"] == key:
            return item
    else:
        return {"key": key}

Interestingly, the diagnostic is printed twice for each key-value pair if the dict is created with {"key": key, "key2": None} and once for each key-value if it's created with dict(key=key, key2=None)

Performance gains via reordering of mathematical expressions

Optimisation Opportunity

The generated bytecode from the expression 1 + a + 1 is inefficient:

              2 LOAD_CONST               0 (1)
              4 LOAD_NAME                0 (a)
              6 BINARY_OP                0 (+)
             10 LOAD_CONST               0 (1)
             12 BINARY_OP                0 (+)

It would be more efficient if the expression was manually const-folded or reordered so that Python all the constants are first, allowing Python to const-fold it. For example, the expression 1 + 1 + a compiles to:

              2 LOAD_CONST               0 (2)
              4 LOAD_NAME                0 (a)
              6 BINARY_OP                0 (+)

As you can see, the 1 + 1 has been const-folded, meaning that one less addition has to be performed.

A Caveat

A Python implementation cannot perform an optimisation like this, because a could have a .__add__ method that is not associative and commutative, meaning that changes like these would result in different behaviour.

However, a linter could freely suggest optimisations of this nature, because they're just suggestions. The user would have to be properly warned that the optimisation should only be applied to ints, however.

What Specifically can be Optimised

Expressions containing commutative and associative operations on ints can be reordered so that constants factors are first. This cannot be done for floats, because floating point operations are neither associative nor commutative (this is why modern compilers don't optimise floating point math by default). However, you could add an option to lint floating point operations in this way as well (e.g., gcc only performs optimisations like this when -fassociative-math is enabled).

Realistically, this would be rather difficult to implement, and the performance gains would not be very large compared to some other possibilities. But I figured I'd post the issue anyway.

Incorrect loop-invariant-global-usage warnings

Given the following code snippet:

x = 5
for y in range(10):
    print(x)
    print(y)

The variable x is already hoisted out of the loop and y cannot be since it depends on the range() iterator. Running perflint over the code gives loop-invariant-global-usage warnings for both variables:

$ perflint repro.py
************* Module repro
repro.py:3:10: W8202: Lookups of global names within a loop is inefficient, copy to a local variable outside of the loop first. (loop-invariant-global-usage)
repro.py:4:10: W8202: Lookups of global names within a loop is inefficient, copy to a local variable outside of the loop first. (loop-invariant-global-usage)

This is using perflint at commit 388d2736:

$ perflint --version
pylint 2.13.7
astroid 2.11.3
Python 3.10.4 (main, Mar 29 2022, 23:31:50) [Clang 10.0.1 (clang-1001.0.46.4)]

invalid loop-invariant-statement detected

Following sample yields incorrect loop-invariant-statement

def test():
    dicts = ({'1': 'a', '2': 'b'}, {'3': 'c'})
    for d in dicts:
        print({int(k): v for k, v in d.items()})

sample.py:4:15: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)

Please add a pre-commit hook

We should be able to run perflint using pre-commit.
Currently, there's no pre-commit-hooks.yml in this repository so we can't use this linter with pre-commit.

R8203 no longer valid for CPython 3.11+

First of all, thanks for perflint, I love it :D

Now, to the issue at hand: CPython 3.11 implemented zero-cost exception handling (1, 2), therefore a try/except block inside a loop is no longer a perf issue, as it would be wrongfully stated by R8203: Try..except blocks have an overhead. Avoid using them inside a loop unless you're using them for control-flow. (loop-try-except-usage).

Now, I do understand that detecting the Python version being used by the script being analyzed might not be at all possible, but maybe it would be nice to clarify in the message that it does not apply to CPython 3.11+, as it would discourage writing actually performant try/except blocks.

Linter crashes when run via VS Code

Perflint works on the command line against the file below, but crashes when run via VS Code.

Here's the command line output:

>perflint src\CursesRenderer.py
************* Module CursesRenderer
src\CursesRenderer.py:57:46: W8201: Consider moving this expression outside of the loop. (loop-invariant-statement)

-----------------------------------
Your code has been rated at 9.75/10

And here's the output I see in the Python log when opening the file in VS Code:

> .\.venv\Scripts\python.exe ~\.vscode\extensions\ms-python.python-2022.5.10891003\pythonFiles\linter.py -m pylint --load-plugins perflint --rcfile ./.pylintrc .\src\CursesRenderer.py
cwd: .
> ./.venv/Scripts/activate.bat && echo 'e8b39361-0157-4923-80e1-22d70d46dee6' && python ~/.vscode/extensions/ms-python.python-2022.5.10891003/pythonFiles/printEnvVariables.py
##########Linting Output - pylint##########


[ERROR 2022-2-30 15:2:44.986]: Linter 'pylint' failed to parse the output '. SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at s.parseMessages (c:\Users\erikd\.vscode\extensions\ms-python.python-2022.5.10891003\out\client\extension.js:2:526510)
    at s.run (c:\Users\erikd\.vscode\extensions\ms-python.python-2022.5.10891003\out\client\extension.js:2:507944)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at s.runLinter (c:\Users\erikd\.vscode\extensions\ms-python.python-2022.5.10891003\out\client\extension.js:2:526025)
import abc
import curses
from Engine import Game, Level, MapPoint, MapTileTypeHelper, UnitVector
from Keyboard import Keyboard

class Renderer(abc.ABC):
    def render(self, game: Game): ...


class CursesRenderer(Renderer, Keyboard):
    def __init__(self):
        self.console = curses.initscr()
        curses.curs_set(0)
        self.console.keypad(True)
        self.console.refresh()

    def render(self, game: Game):
        self._drawLevel(game.level)
        self.console.refresh()

    def readKey(self) -> str:
        return self.console.getch()

    def tryGetUnitVector(self, key: str) -> UnitVector | None:
        match key:
            case "7":
                return UnitVector.NW
            case curses.KEY_UP | "8":
                return UnitVector.N
            case "9":
                return UnitVector.NE
            case curses.KEY_RIGHT | "6":
                return UnitVector.E
            case "3":
                return UnitVector.SE
            case curses.KEY_DOWN | "2":
                return UnitVector.S
            case "1":
                return UnitVector.SW
            case curses.KEY_LEFT | "4":
                return UnitVector.W
            case _:
                return None

    def _drawLevel(self, level: Level):
        for pointAndTile in level.map.tiles:
            self._drawAt(MapTileTypeHelper.getGlyph(pointAndTile[1].type) if pointAndTile[1].isExplored else " ", pointAndTile[0])
        
        for obj in level.knownObjects:
            self._drawAt(obj.glyph, obj.position)

    def _drawAt(self, glyph: str, point: MapPoint):
        self.console.addch(point.y, point.x, glyph)

    def _drawLogString(self, logEntry: str):
        for i in range(0, self.console.width):
            charToRender = logEntry[i] if i < len(logEntry) else " "
            self._drawAt(charToRender, MapPoint(i, self.console.height - 1))

Migrate to pylint v3

perflint requires a version of pylint older than v3, which won’t allow users to move forward anymore:

dependencies = ["pylint >= 2.12.0, < 3.0"]

The following patch should at least get the plugin working, though I’ve not yet read thoroughly through the code yet:

diff --git a/perflint/comprehension_checker.py b/perflint/comprehension_checker.py
index 9ae4b84..f7ebd8c 100644
--- a/perflint/comprehension_checker.py
+++ b/perflint/comprehension_checker.py
@@ -1,7 +1,6 @@
 from astroid import nodes
 from pylint.checkers import BaseChecker
 from pylint.checkers import utils as checker_utils
-from pylint.interfaces import IAstroidChecker
 from astroid.helpers import safe_infer
 
 
@@ -10,8 +9,6 @@ class ComprehensionChecker(BaseChecker):
     Check for comprehension usage
     """
 
-    __implements__ = IAstroidChecker
-
     name = "comprehension-checker"
     priority = -1
     msgs = {
@@ -35,7 +32,7 @@ class ComprehensionChecker(BaseChecker):
     def visit_for(self, node: nodes.For):
         pass
 
-    @checker_utils.check_messages(
+    @checker_utils.only_required_for_messages(^M
         "use-list-comprehension", "use-dict-comprehension", "use-list-copy"
     )
     def leave_for(self, node: nodes.For):
diff --git a/perflint/for_loop_checker.py b/perflint/for_loop_checker.py
index c17d4a8..f1f97f8 100644
--- a/perflint/for_loop_checker.py
+++ b/perflint/for_loop_checker.py
@@ -3,7 +3,6 @@ from astroid import nodes
 from astroid.helpers import safe_infer
 from pylint.checkers import BaseChecker
 from pylint.checkers import utils as checker_utils
-from pylint.interfaces import IAstroidChecker
 
 iterable_types = (
     nodes.Tuple,
@@ -52,8 +51,6 @@ class ForLoopChecker(BaseChecker):
     Check for poor for-loop usage.
     """
 
-    __implements__ = IAstroidChecker
-
     name = "for-loop-checker"
     priority = -1
     msgs = {
@@ -69,7 +66,7 @@ class ForLoopChecker(BaseChecker):
         ),
     }
 
-    @checker_utils.check_messages(
+    @checker_utils.only_required_for_messages(
         "unnecessary-list-cast", "incorrect-dictionary-iterator"
     )
     def visit_for(self, node: nodes.For) -> None:
@@ -130,8 +127,6 @@ class LoopInvariantChecker(BaseChecker):
     Check for poor for-loop usage.
     """
 
-    __implements__ = IAstroidChecker
-
     name = "loop-invariant-checker"
     priority = -1
     msgs = {
@@ -170,7 +165,7 @@ class LoopInvariantChecker(BaseChecker):
         self._loop_consts: List[List[nodes.Const]] = []
         self._ignore: List[nodes.NodeNG] = []
 
-    @checker_utils.check_messages("loop-invariant-statement")
+    @checker_utils.only_required_for_messages("loop-invariant-statement")
     def visit_for(self, node: nodes.For) -> None:
         """Visit for loop bodies."""
         self._loop_level += 1
@@ -184,7 +179,7 @@ class LoopInvariantChecker(BaseChecker):
         self._loop_consts.append([])
         self._ignore.append(node.iter)
 
-    @checker_utils.check_messages("loop-invariant-statement")
+    @checker_utils.only_required_for_messages("loop-invariant-statement")
     def visit_while(self, node: nodes.While) -> None:
         """Visit while loop bodies."""
         self._loop_level += 1
@@ -214,11 +209,11 @@ class LoopInvariantChecker(BaseChecker):
         ):
             self._ignore.append(node)
 
-    @checker_utils.check_messages("loop-invariant-statement")
+    @checker_utils.only_required_for_messages("loop-invariant-statement")
     def leave_for(self, node: nodes.For) -> None:
         self._leave_loop(node)
 
-    @checker_utils.check_messages("loop-invariant-statement")
+    @checker_utils.only_required_for_messages("loop-invariant-statement")
     def leave_while(self, node: nodes.While) -> None:
         self._leave_loop(node)
 
@@ -303,7 +298,7 @@ class LoopInvariantChecker(BaseChecker):
         if isinstance(node.target, nodes.AssignName):
             self._loop_assignments[-1].add(node.target.name)
 
-    @checker_utils.check_messages("loop-global-usage")
+    @checker_utils.only_required_for_messages("loop-global-usage")
     def visit_name(self, node: nodes.Name) -> None:
         """Look for global names"""
         if self._loop_names:
@@ -338,12 +333,12 @@ class LoopInvariantChecker(BaseChecker):
         if isinstance(node.func.expr, nodes.Name):
             self._loop_assignments[-1].add(node.func.expr.name)
 
-    @checker_utils.check_messages("loop-try-except-usage")
-    def visit_tryexcept(self, node: nodes.TryExcept) -> None:
+    @checker_utils.only_required_for_messages("loop-try-except-usage")
+    def visit_tryexcept(self, node: nodes.Try) -> None:
         if self._loop_level > 0:
             self.add_message("loop-try-except-usage", node=node)
 
-    @checker_utils.check_messages("memoryview-over-bytes")
+    @checker_utils.only_required_for_messages("memoryview-over-bytes")
     def visit_subscript(self, node: nodes.Subscript) -> None:
         if self._loop_level == 0:
             return
@@ -360,7 +355,7 @@ class LoopInvariantChecker(BaseChecker):
         ):
             self.add_message("memoryview-over-bytes", node=node)
 
-    @checker_utils.check_messages("dotted-import-in-loop")
+    @checker_utils.only_required_for_messages("dotted-import-in-loop")
     def visit_attribute(self, node: nodes.Attribute) -> None:
         if self._loop_level == 0:
             return
diff --git a/perflint/list_checker.py b/perflint/list_checker.py
index 681ed51..9ded539 100644
--- a/perflint/list_checker.py
+++ b/perflint/list_checker.py
@@ -2,7 +2,6 @@ from typing import Dict, List
 from astroid import nodes
 from pylint.checkers import BaseChecker
 from pylint.checkers import utils as checker_utils
-from pylint.interfaces import IAstroidChecker
 
 
 class ListChecker(BaseChecker):
@@ -10,8 +9,6 @@ class ListChecker(BaseChecker):
     Check for inefficient list usage
     """
 
-    __implements__ = IAstroidChecker
-
     name = 'list-checker'
     priority = -1
     msgs = {
@@ -42,14 +39,14 @@ class ListChecker(BaseChecker):
         for _assignment in _lists.values():
             self.add_message("use-tuple-over-list", node=_assignment.parent.value)
 
-    @checker_utils.check_messages("use-tuple-over-list")
+    @checker_utils.only_required_for_messages("use-tuple-over-list")
     def leave_module(self, node: nodes.Module):
         self._raise_for_scope()
 
     def visit_functiondef(self, node: nodes.FunctionDef):
         self._lists_to_watch.append({})
 
-    @checker_utils.check_messages("use-tuple-over-list")
+    @checker_utils.only_required_for_messages("use-tuple-over-list")
     def leave_functiondef(self, node: nodes.FunctionDef):
         self._raise_for_scope()
 
diff --git a/pyproject.toml b/pyproject.toml
index a7ca680..386adc1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,7 @@ authors = [{name = "Anthony Shaw"}]
 readme = "README.md"
 classifiers = ["License :: OSI Approved :: MIT License"]
 dynamic = ["version", "description"]
-dependencies = ["pylint >= 2.12.0, < 3.0"]
+dependencies = ["pylint >=3.0.0,<4.0.0"]
 
 [project.urls]
 Home = "https://github.com/tonybaloney/perflint"

Options ignored on the command line

On Windows, Mambaforge Python 3.9.7 environment:

perflint mypackage/ --disable=loop-invariant-global-usage
...
mypackage\__main__.py:122:65: W8202: Lookups of global names within a loop is inefficient, copy to a local variable outside of the loop first. (loop-invariant-global-usage

Same with -d W8202.

Comprehensions can only have If with no Elif/Else

This is being raised as a potential comprehension, which is impossible

def should_not_be_a_list_comprehension(args):
    """Internal helper for get_args."""
    res = []
    for arg in args:
        if not isinstance(arg, tuple):
            res.append(arg)
        elif is_callable_type(arg[0]):
            if len(arg) == 2:
                res.append(Callable[[], arg[1]])
            elif arg[1] is Ellipsis:
                res.append(Callable[..., arg[2]])
            else:
                res.append(Callable[list(arg[1:-1]), arg[-1]])
        else:
            res.append(type(arg[0]).__getitem__(arg[0], _eval_args(arg[1:])))
    return tuple(res)

AttributeError: 'Subscript' object has no attribute 'name'

I think this belongs here, but please let me know if it should go to pylint instead.

Python 3.10.2
macOS Monterey
pylint==2.12.2
perflint==0.0.9

$ git clone https://github.com/python-pillow/Pillow
...
$ cd Pillow
$ pylint src/PIL/PngImagePlugin.py
...

-----------------------------------
Your code has been rated at 6.99/10

$ pylint src/PIL/PngImagePlugin.py  --load-plugins=perflint
...
Exception on node <Subscript l.1136 at 0x104ca3bb0> in file '/private/tmp/Pillow/src/PIL/PngImagePlugin.py'
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pylint/utils/ast_walker.py", line 72, in walk
    callback(astroid)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/perflint/for_loop_checker.py", line 248, in visit_subscript
    inferred_value = local_type(node.value)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/perflint/for_loop_checker.py", line 19, in local_type
    if name.name in name.frame().locals:
AttributeError: 'Subscript' object has no attribute 'name'
src/PIL/PngImagePlugin.py:1:0: F0001: Fatal error while checking 'src/PIL/PngImagePlugin.py'. Please open an issue in our bug tracker so we address this. There is a pre-filled template that you can use in '/Users/huvankem/Library/Caches/pylint/pylint-crash-2022-03-01-17.txt'. (fatal)

------------------------------------------------------------------
Your code has been rated at 6.02/10 (previous run: 6.99/10, -0.97)

Here's the crash file:

pylint-crash-2022-03-01-17.txt

Doesn't work on Python 3.12

When I run perflint for my code on Python 3.12 I got a lot of errors related to astroid for example:

AttributeError: 'TreeRebuilder' object has no attribute 'visit_typealias'

In additional, perflint is not compatible with pylint 3.x.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.