[perflint] Ignore rule if target is global or nonlocal (PERF401) (#19539)

## Summary

Resolves #19531

I've implemented a check to determine whether the for_stmt target is
declared as global or nonlocal. I believe we should skip the rule in all
such cases, since variables declared this way are intended for use
outside the loop scope, making value changes expected behavior.

## Test Plan

Added two test cases for global and nonlocal variable to snapshot.
This commit is contained in:
Igor Drokin 2025-07-29 00:03:22 +03:00 committed by GitHub
parent 4016aff057
commit e4f64480da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 28 additions and 1 deletions

View file

@ -289,4 +289,19 @@ def f():
i = "xyz" i = "xyz"
result = [] result = []
for i in range(3): for i in range(3):
result.append((x for x in [i])) result.append((x for x in [i]))
G_INDEX = None
def f():
global G_INDEX
result = []
for G_INDEX in range(3):
result.append(G_INDEX)
def f():
NL_INDEX = None
def x():
nonlocal NL_INDEX
result = []
for NL_INDEX in range(3):
result.append(NL_INDEX)

View file

@ -249,6 +249,11 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
.iter() .iter()
.find(|binding| for_stmt.target.range() == binding.range) .find(|binding| for_stmt.target.range() == binding.range)
.unwrap(); .unwrap();
// If the target variable is global (e.g., `global INDEX`) or nonlocal (e.g., `nonlocal INDEX`),
// then it is intended to be used elsewhere outside the for loop.
if target_binding.is_global() || target_binding.is_nonlocal() {
return;
}
// If any references to the loop target variable are after the loop, // If any references to the loop target variable are after the loop,
// then converting it into a comprehension would cause a NameError // then converting it into a comprehension would cause a NameError
if target_binding if target_binding

View file

@ -263,5 +263,7 @@ PERF401.py:292:9: PERF401 Use a list comprehension to create a transformed list
291 | for i in range(3): 291 | for i in range(3):
292 | result.append((x for x in [i])) 292 | result.append((x for x in [i]))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
293 |
294 | G_INDEX = None
| |
= help: Replace for loop with list comprehension = help: Replace for loop with list comprehension

View file

@ -612,6 +612,8 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
291 | for i in range(3): 291 | for i in range(3):
292 | result.append((x for x in [i])) 292 | result.append((x for x in [i]))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
293 |
294 | G_INDEX = None
| |
= help: Replace for loop with list comprehension = help: Replace for loop with list comprehension
@ -623,3 +625,6 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
291 |- for i in range(3): 291 |- for i in range(3):
292 |- result.append((x for x in [i])) 292 |- result.append((x for x in [i]))
290 |+ result = [(x for x in [i]) for i in range(3)] 290 |+ result = [(x for x in [i]) for i in range(3)]
293 291 |
294 292 | G_INDEX = None
295 293 | def f():