Skip to content

perflint: Add ruff rules for performance - #21

Open
cclauss wants to merge 1 commit into
ARCLab-MIT:mainfrom
cclauss:ruff-rules-perflint
Open

cclauss wants to merge 1 commit into
ARCLab-MIT:mainfrom
cclauss:ruff-rules-perflint

Conversation

@cclauss

@cclauss cclauss commented Sep 27, 2025 •

Copy link
Copy Markdown
Contributor

What this does

perflint: Add ruff rules for performance -- https://docs.astral.sh/ruff/rules/#perflint-perf


How it was tested

% pre-commit run --all-files

% ruff rule PERF401

manual-list-comprehension (PERF401)

Derived from the Perflint linter.

Fix is sometimes available.

What it does

Checks for for loops that can be replaced by a list comprehension.

Why is this bad?

When creating a transformed list from an existing list using a for-loop,
prefer a list comprehension. List comprehensions are more readable and
more performant.

Using the below as an example, the list comprehension is ~10% faster on
Python 3.11, and ~25% faster on Python 3.10.

Note that, as with all perflint rules, this is only intended as a
micro-optimization, and will have a negligible impact on performance in
most cases.

Example

original = list(range(10000))
filtered = []
for i in original:
    if i % 2:
        filtered.append(i)

Use instead:

original = list(range(10000))
filtered = [x for x in original if x % 2]

If you're appending to an existing list, use the extend method instead:

original = list(range(10000))
filtered.extend(x for x in original if x % 2)

Copilot AI review requested due to automatic review settings September 27, 2025 05:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds Ruff's perflint performance rules to improve code efficiency by addressing common performance anti-patterns. The changes focus on optimizing dictionary/list operations and reducing unnecessary iterations.

  • Replaced dict.items() with dict.values() when keys aren't used
  • Converted manual list building loops to list comprehensions
  • Added perflint rules to the linting configuration

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pyproject.toml Added PERF rules to Ruff configuration with selective ignores
src/beavr/teleop/components/operator/solvers/leap_solver.py Added noqa comment for dictionary assignment in loop
src/beavr/lerobot/configs/policies.py Optimized dictionary iteration by using .values()
src/beavr/lerobot/common/robot_devices/robots/mobile_manipulator.py Replaced manual list building with list comprehensions
src/beavr/lerobot/common/robot_devices/robots/manipulator.py Converted loops to list comprehensions for state/action creation
src/beavr/lerobot/common/robot_devices/robots/beavrbot.py Optimized dictionary iteration by using .values()
src/beavr/lerobot/common/robot_devices/cameras/opencv.py Refactored camera finding logic with list comprehensions
src/beavr/lerobot/common/policies/smolvla/smolvlm_with_expert.py Replaced append loop with list.extend() and generator expression

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +61 to +73
print(
"Mac or Windows detected. Finding available camera indices through "
f"scanning all indices from 0 to {MAX_OPENCV_INDEX}"
)
possible_indices = range(max_index_search_range)
indices = _find_cameras(possible_indices, mock=mock)
return [
{
"port": None,
"index": index,
}
for index in indices
]

Copilot AI Sep 27, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linux branch returns early, but the non-Linux code now runs unconditionally. This should be wrapped in an else block to maintain the original behavior.

Copilot uses AI. Check for mistakes.

@cclauss cclauss Sep 27, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would violate ruff rule RET505.

The else statement is not needed as the return statement will always break out of the enclosing function. Removing the else will reduce nesting and make the code more readable.

Comment thread pyproject.toml Outdated
# TODO: Uncomment rules when ready to use
select = [
"E", "W", "F", "I", "B", "C4", "T20", "N" # "SIM", "A", "S", "D", "RUF", "UP"
"ASYNC", "B", "C4", "E", "F", "I", "N", "PERF", "T20", "W" # "SIM", "A", "S", "D", "RUF", "UP"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep these sorted to make it easy to spot missing rules and nearly impossible to add duplicates.

@cclauss
cclauss requested a review from Copilot September 27, 2025 06:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 48 to 49
def find_cameras(raise_when_empty=False, max_index_search_range=MAX_OPENCV_INDEX, mock=False) -> list[dict]:
cameras = []
if platform.system() == "Linux":

Copilot AI Sep 27, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature indicates it returns list[dict] but the implementation has two different return paths that may not execute. After the Linux block, there's no else clause, so the Mac/Windows code will always execute regardless of platform, potentially causing the Linux case to return twice or fall through incorrectly.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both possible paths return a list[dict]. The fall through is correct because Mac/Windows code will never execute in the Linux case and the Linux code will never execute in the Mac/Windows case. Returning twice is not possible in Python code because the first return leaves the function.

Comment thread pyproject.toml Outdated
@cclauss
cclauss force-pushed the ruff-rules-perflint branch from f01fd96 to a59f59d Compare September 27, 2025 07:14
@cclauss
cclauss force-pushed the ruff-rules-perflint branch from a59f59d to 42025e0 Compare December 29, 2025 12:32
Copilot AI review requested due to automatic review settings December 29, 2025 12:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if platform.system() == "Linux":
print("Linux detected. Finding available camera indices through scanning '/dev/video*' ports")
possible_ports = [str(port) for port in Path("/dev").glob("video*")]
ports = _find_cameras(possible_ports, mock=mock)

Copilot AI Dec 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactoring removed the early return logic and changed the control flow. The raise_when_empty parameter is no longer being passed to the _find_cameras function calls. The original code had implicit fall-through to the else block, but now when the Linux branch returns early, the function doesn't handle the raise_when_empty parameter for either path.

Copilot uses AI. Check for mistakes.
f"scanning all indices from 0 to {MAX_OPENCV_INDEX}"
)
possible_indices = range(max_index_search_range)
indices = _find_cameras(possible_indices, mock=mock)

Copilot AI Dec 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactoring removed the early return logic and changed the control flow. The raise_when_empty parameter is no longer being passed to the _find_cameras function call. The original code passed this parameter through, but now it's missing.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants