MAINT Enable ruff PERF rule for performance linting#1415
MAINT Enable ruff PERF rule for performance linting#1415romanlutz wants to merge 1 commit intoAzure:mainfrom
Conversation
romanlutz
commented
Feb 27, 2026
- Add PERF to ruff select list
- Ignore PERF203 (try-except in loop is intentional)
- Fix PERF401: use list comprehensions/extend instead of for-loop appends
- Fix PERF402: use list() instead of unnecessary comprehension
- Fix PERF403: use dict comprehension instead of for-loop updates
- Fix PERF102: use .keys()/.values() instead of iterating items()
- Add noqa for PERF401 in auxiliary_attacks (external code)
There was a problem hiding this comment.
Pull request overview
This maintenance PR enables the ruff PERF rule set (perflint) for performance-oriented linting and addresses all resulting violations across the codebase. The changes are purely mechanical refactors with no behavioral differences.
Changes:
- Adds
PERFto the ruffselectlist inpyproject.toml(and ignoresPERF203for intentional try-except in loops) - Fixes PERF401/402/403/102 violations: replaces for-loop-with-appends with list comprehensions, unnecessary comprehensions with
list(), for-loop dict updates withdict.update(), and.items()iterations that only use values with.values() - Adds a
# noqa: PERF401comment in external GCG attack code to suppress the rule there
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
pyproject.toml |
Adds PERF to select and PERF203 to ignore |
pyrit/auxiliary_attacks/gcg/attack/base/attack_manager.py |
Converts for-loop to list comprehension + adds (misapplied) noqa comment |
pyrit/analytics/conversation_analytics.py |
Replaces manual loop+append with list comprehension |
pyrit/executor/attack/printer/markdown_printer.py |
Replaces multiple loop+append patterns with .extend() and generators |
pyrit/executor/promptgen/fuzzer/fuzzer.py |
Replaces manual loop+append with list comprehension |
pyrit/identifiers/component_identifier.py |
Replaces loop dict-update with dict comprehension + update() |
pyrit/memory/memory_exporter.py |
Replaces two manual loop+appends with list comprehensions |
pyrit/memory/memory_interface.py |
Replaces loop+append with conditions.extend() |
pyrit/models/scenario_result.py |
Replaces loop+append with objectives.extend() |
pyrit/prompt_converter/audio_*.py (3 files) |
Replaces loop+append for channels with list comprehension |
pyrit/prompt_converter/nato_converter.py |
Replaces loop+append with list comprehension |
pyrit/prompt_target/openai/openai_chat_target.py |
Replaces loop dict-update with dict.update() |
pyrit/prompt_target/openai/openai_response_target.py |
Replaces loop+append with list comprehension |
pyrit/scenario/core/scenario_strategy.py |
Replaces loop+append with .extend() and generator |
pyrit/scenario/scenarios/airt/*.py (3 files) |
Replaces loop+append with list comprehensions |
pyrit/scenario/scenarios/foundry/red_team_agent.py |
Replaces loop+append with list comprehension |
pyrit/scenario/scenarios/garak/encoding.py |
Replaces loop+append with list comprehension |
pyrit/score/scorer.py |
Replaces loop+append with list comprehension |
pyrit/score/scorer_evaluation/scorer_metrics_io.py |
Replaces loop dict-update with dict comprehension + update() |
pyrit/setup/initializers/pyrit_initializer.py |
Replaces .items() iteration (only using keys) with direct dict iteration |
tests/unit/datasets/test_jailbreak_text.py |
Replaces .items() where value was unused with .values() |
tests/unit/executor/attack/test_attack_parameter_consistency.py |
Replaces loop+append with .extend() and generator |
tests/unit/registry/test_base_instance_registry.py |
Replaces loop+append with list() |
tests/unit/setup/test_load_default_datasets.py |
Replaces loop+append with .extend() and generator |
tests/unit/target/test_playwright_target.py |
Replaces loop+append with list comprehension |
| for i in range(3, tokenizer.vocab_size): | ||
| if not is_ascii(tokenizer.decode([i])): | ||
| ascii_toks.append(i) | ||
| ascii_toks = [i for i in range(3, tokenizer.vocab_size) if not is_ascii(tokenizer.decode([i]))] # noqa: PERF401 |
There was a problem hiding this comment.
The # noqa: PERF401 comment on this line is misapplied. PERF401 flags for-loops that build lists using .append() and can be replaced with a list comprehension. However, line 110 IS already a list comprehension, so PERF401 would never fire on it in the first place.
The PR description states the intent was to "Add noqa for PERF401 in auxiliary_attacks (external code)". The correct approach for preserving external code as-is would be to keep the original for-loop and add the # noqa: PERF401 there. Instead, the code was converted to a list comprehension (changing the external code) AND the now-meaningless noqa was added.
The noqa comment should either be removed (since the list comprehension doesn't trigger PERF401) or the original for-loop form should be restored with the noqa suppression, consistent with the stated intent of not modifying external code.
| ascii_toks = [i for i in range(3, tokenizer.vocab_size) if not is_ascii(tokenizer.decode([i]))] # noqa: PERF401 | |
| ascii_toks = [i for i in range(3, tokenizer.vocab_size) if not is_ascii(tokenizer.decode([i]))] |
| "DOC", # https://docs.astral.sh/ruff/rules/#pydoclint-doc | ||
| "F401", # unused-import | ||
| "I", # isort | ||
| "PERF", # https://docs.astral.sh/ruff/rules/#perflint-perf |
There was a problem hiding this comment.
The PERF rule prefix was added to select but not to fixable. Looking at other multi-character rule codes in the fixable list (PIE, RET, SIM, TCH, etc.), there's a consistent pattern of including selected multi-character rule codes in fixable as well. Without PERF in the fixable list, ruff --fix will not auto-fix any future PERF violations, requiring manual fixes every time. Consider adding "PERF" to the fixable list for consistency.
- Add PERF to ruff select list - Ignore PERF203 (try-except in loop is intentional) - Fix PERF401: use list comprehensions/extend instead of for-loop appends - Fix PERF402: use list() instead of unnecessary comprehension - Fix PERF403: use dict comprehension instead of for-loop updates - Fix PERF102: use .keys()/.values() instead of iterating items() - Add noqa for PERF401 in auxiliary_attacks (external code) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f4a907b to
3a8b87b
Compare