ruff FURB154
labelled unknown
Analysis Nemotron 3 Super
Flags consecutive 'global' or 'nonlocal' statements declaring separate variables when they could be combined into a single statement.
Fix: Replaces consecutive 'global' or 'nonlocal' statements with a single statement that combines all declared variables into a comma-separated list, preserving the order of appearance.
8 hazards
- Moving a 'global' statement after a comment would detach the comment from the statement, altering readability and potentially meaning if the comment was intended as documentation for that specific declaration.
- If a 'global' statement is followed by an inline comment on the same line, the fix would drop that comment because it only preserves comments on separate lines before the first statement.
- When 'global' and 'nonlocal' statements are interleaved, combining them into one statement is invalid syntax because 'global' and 'nonlocal' cannot be mixed in the same statement.
- If a variable name appears in both a 'global' and a subsequent 'nonlocal' statement (or vice versa), combining them would create a conflicting declaration that is invalid in Python scope rules.
- Placing a combined 'global' statement before other statements that use those variables could change behavior if the original separate statements were guarded by a conditional that executed later, though this is prevented by the rule requiring consecutive statements.
- If the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire function scope regardless of position.
- In rare cases where a 'global' statement is used to override a built-in and the order of overriding matters for side effects in imported modules, combining statements does not change the order since all declarations happen simultaneously at function entry.
- If a 'global' statement is split across multiple lines using backslash continuation, the fix would not apply because the rule only detects simple statement lines, so no hazard arises from the fix itself in such cases.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Evaluation Order Hazard | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r1-p002 | fix ok | Parameter Defaults Hazard | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r1-p003 | fix ok prediction off | Backslash Continuation Near-Miss | If a 'global' statement is split across multiple lines using backslash continuation, the fix would not apply because the rule only detects simple statement lines, so no hazard arises from the fix itse |
| r1-p004 | fix ok | Exception Handling Hazard | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p005 | fix ok | Async Generator Hazard | async def/await ordering, async generators |
| r1-p006 | fix ok | File Write Hazard | file modes and truncation, partial writes, non-str data passed to write |
| r1-p007 | fix ok | Nested Function Scope Hazard | If the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire |
| r1-p008 | fix ok | Built-in Override Hazard | In rare cases where a 'global' statement is used to override a built-in and the order of overriding matters for side effects in imported modules, combining statements does not change the order since a |
| r2-p001 | fix ok | String Formatting Edge Cases | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p002 | fix ok | Keyword-only, Positional-only, Args/Kwargs Forwarding | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p003 | fix ok | File Modes and Truncation | file modes and truncation, partial writes, non-str data passed to write |
| r2-p004 | fix ok | Async Def/Await Ordering | async def/await ordering, async generators |
| r2-p005 | fix ok | Mutable Default Arguments and Aliasing | mutable default arguments and aliasing after a rewrite |
| r2-p006 | fix ok | Nested Function References Same Name | If the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire |
| r2-p007 | fix ok | Walrus, Comprehensions Scoping, Late Binding | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p008 | fix ok | Generators vs Lists, Iterators Consumed Once | generators vs lists, iterators consumed once, lazy vs eager evaluation |
Sources
r1-p001 · Evaluation Order Hazard
1def f():
2 i = [0]
3 global a
4 global b
5 a = i[0]; i[0] += 1
6 b = i[0]; i[0] += 1
7 return a + b
8print(f())
--- orig.py 2026-10-17 17:33:10.316609135 -0400 +++ fixed.py 2026-10-17 17:33:10.475607036 -0400 @@ -1,7 +1,6 @@ def f(): i = [0] - global a - global b + global a, b a = i[0]; i[0] += 1 b = i[0]; i[0] += 1 return a + b
1
r1-p002 · Parameter Defaults Hazard
1defaults = []
2def f(x=defaults.append(1) or 2):
3 global a
4 global b
5 return x
6print(f())
7print(f())
8print(defaults)
--- orig.py 2026-10-17 17:33:10.589605531 -0400 +++ fixed.py 2026-10-17 17:33:10.762603247 -0400 @@ -1,7 +1,6 @@ defaults = [] def f(x=defaults.append(1) or 2): - global a - global b + global a, b return x print(f()) print(f())
2 2 [1]
r1-p003 · Backslash Continuation Near-Miss
1def f():
2 global \
3 a
4 global \
5 b
6 a = 1
7 b = 2
8 return a + b
9print(f())
--- orig.py 2026-10-17 17:33:10.862601927 -0400 +++ fixed.py 2026-10-17 17:33:11.033599670 -0400 @@ -1,8 +1,5 @@ def f(): - global \ - a - global \ - b + global a, b a = 1 b = 2 return a + b
3
r1-p004 · Exception Handling Hazard
1def f():
2 global a
3 global b
4 try:
5 a = 1 / 0
6 except ZeroDivisionError as e:
7 b = 42
8 raise RuntimeError('failed') from e
9 else:
10 b = 99
11 finally:
12 a = -1
13try:
14 f()
15except RuntimeError as e:
16 print('caught:', type(e).__name__, str(e))
17 print('cause:', type(e.__cause__).__name__ if e.__cause__ else None)
18 print('a=', a, 'b=', b)
--- orig.py 2026-10-17 17:33:11.128598417 -0400 +++ fixed.py 2026-10-17 17:33:11.296596202 -0400 @@ -1,6 +1,5 @@ def f(): - global a - global b + global a, b try: a = 1 / 0 except ZeroDivisionError as e:
caught: RuntimeError failed cause: ZeroDivisionError a= -1 b= 42
r1-p005 · Async Generator Hazard
1async def f():
2 global x
3 global y
4 x = 1
5 await asyncio.sleep(0)
6 y = 2
7 yield x + y
8import asyncio
9async def main():
10 gen = f()
11 val = await gen.__anext__()
12 print('value:', val)
13 print('x=', x, 'y=', y)
14asyncio.run(main())
--- orig.py 2026-10-17 17:33:11.399594843 -0400 +++ fixed.py 2026-10-17 17:33:11.703590835 -0400 @@ -1,6 +1,5 @@ async def f(): - global x - global y + global x, y x = 1 await asyncio.sleep(0) y = 2
value: 3 x= 1 y= 2
r1-p006 · File Write Hazard
1def f():
2 global log
3 global out
4 log = open('log.txt', 'w')
5 out = open('out.txt', 'w')
6 log.write('start\n')
7 out.write(b'bad')
8 log.close()
9 out.close()
10try:
11 f()
12except Exception as e:
13 print(type(e).__name__, str(e))
14finally:
15 try:
16 print(open('log.txt').read().strip())
17 except:
18 print('log: <error>')
19 try:
20 print(open('out.txt', 'rb').read())
21 except:
22 print('out: <error>')
--- orig.py 2026-10-17 17:33:11.867588672 -0400 +++ fixed.py 2026-10-17 17:33:12.031586510 -0400 @@ -1,6 +1,5 @@ def f(): - global log - global out + global log, out log = open('log.txt', 'w') out = open('out.txt', 'w') log.write('start\n')
TypeError write() argument must be str, not bytes b''
r1-p007 · Nested Function Scope Hazard
1def f():
2 global x
3 global y
4 def inner():
5 return x + y
6 x = 10
7 y = 20
8 return inner()
9print(f())
10print('x=', x, 'y=', y)
--- orig.py 2026-10-17 17:33:12.137585114 -0400 +++ fixed.py 2026-10-17 17:33:12.306582889 -0400 @@ -1,6 +1,5 @@ def f(): - global x - global y + global x, y def inner(): return x + y x = 10
30 x= 10 y= 20
r1-p008 · Built-in Override Hazard
1import math
2original_len = len
3def f():
4 global len
5 global sum
6 len = lambda x: 99
7 sum = lambda x: 88
8 return len([1,2,3]) + sum([4,5])
9print(f())
10print('len overridden:', len == (lambda x: 99))
11print('sum overridden:', sum == (lambda x: 88))
12print('math.len unchanged:', math.len is original_len)
--- orig.py 2026-10-17 17:33:12.411581504 -0400 +++ fixed.py 2026-10-17 17:33:12.807576290 -0400 @@ -1,8 +1,7 @@ import math original_len = len def f(): - global len - global sum + global len, sum len = lambda x: 99 sum = lambda x: 88 return len([1,2,3]) + sum([4,5])
187 len overridden: False sum overridden: False
r2-p001 · String Formatting Edge Cases
1def f():
2 global a
3 global b
4 s = f'{a} {b}'
5 print(s)
6a = 1
7b = 2
8f()
--- orig.py 2026-10-17 17:33:17.690512217 -0400 +++ fixed.py 2026-10-17 17:33:17.857510033 -0400 @@ -1,6 +1,5 @@ def f(): - global a - global b + global a, b s = f'{a} {b}' print(s) a = 1
1 2
r2-p002 · Keyword-only, Positional-only, Args/Kwargs Forwarding
1def f(a, /, b, *, c=[]):
2 global d
3 global e
4 return a + b + len(c) + d + e
5d = 10
6e = 20
7print(f(1, 2, c=[3]))
--- orig.py 2026-10-17 17:33:17.958508711 -0400 +++ fixed.py 2026-10-17 17:33:18.129506476 -0400 @@ -1,6 +1,5 @@ def f(a, /, b, *, c=[]): - global d - global e + global d, e return a + b + len(c) + d + e d = 10 e = 20
34
r2-p003 · File Modes and Truncation
1def f():
2 global log
3 global out
4 log.write('start')
5 out.write(b'data')
6try:
7 with open('tmp.txt', 'w') as log, open('tmp.bin', 'wb') as out:
8 f()
9except Exception as ex:
10 print(type(ex).__name__, ex)
11finally:
12 import os
13 try: os.remove('tmp.txt'); os.remove('tmp.bin')
14 except: pass
--- orig.py 2026-10-17 17:33:18.224505235 -0400 +++ fixed.py 2026-10-17 17:33:18.396502987 -0400 @@ -1,6 +1,5 @@ def f(): - global log - global out + global log, out log.write('start') out.write(b'data') try:
r2-p004 · Async Def/Await Ordering
1async def f():
2 global x
3 global y
4 await asyncio.sleep(0)
5 return x + y
6x = 1
7y = 2
8import asyncio
9print(asyncio.run(f()))
--- orig.py 2026-10-17 17:33:18.493501720 -0400 +++ fixed.py 2026-10-17 17:33:18.801497696 -0400 @@ -1,6 +1,5 @@ async def f(): - global x - global y + global x, y await asyncio.sleep(0) return x + y x = 1
3
r2-p005 · Mutable Default Arguments and Aliasing
1def f(lst=[]):
2 global a
3 global b
4 lst.append(a)
5 lst.append(b)
6 return lst
7a = 10
8b = 20
9print(f())
10print(f())
--- orig.py 2026-10-17 17:33:18.975495422 -0400 +++ fixed.py 2026-10-17 17:33:19.140493268 -0400 @@ -1,6 +1,5 @@ def f(lst=[]): - global a - global b + global a, b lst.append(a) lst.append(b) return lst
[10, 20] [10, 20, 10, 20]
r2-p006 · Nested Function References Same Name
1def f():
2 global x
3 global y
4 def inner():
5 return x + y
6 x = 1
7 y = 2
8 return inner()
9x = 0
10y = 0
11print(f())
--- orig.py 2026-10-17 17:33:19.244491911 -0400 +++ fixed.py 2026-10-17 17:33:19.399489889 -0400 @@ -1,6 +1,5 @@ def f(): - global x - global y + global x, y def inner(): return x + y x = 1
3
r2-p007 · Walrus, Comprehensions Scoping, Late Binding
1def f():
2 global a
3 global b
4 funcs = [(lambda: a + b) for _ in range(2) if (tmp := a) is not None]
5 a = 10
6 b = 20
7 return [g() for g in funcs]
8a = 0
9b = 0
10print(f())
--- orig.py 2026-10-17 17:33:19.506488492 -0400 +++ fixed.py 2026-10-17 17:33:19.664486431 -0400 @@ -1,6 +1,5 @@ def f(): - global a - global b + global a, b funcs = [(lambda: a + b) for _ in range(2) if (tmp := a) is not None] a = 10 b = 20
[30, 30]
r2-p008 · Generators vs Lists, Iterators Consumed Once
1def f():
2 global data
3 global idx
4 data = [1, 2, 3]
5 idx = 0
6 def gen():
7 while idx < len(data):
8 yield data[idx]
9 idx += 1
10 return gen()
11g = f()
12print(list(g))
13print(list(g))
--- orig.py 2026-10-17 17:33:19.768485074 -0400 +++ fixed.py 2026-10-17 17:33:20.152480065 -0400 @@ -1,6 +1,5 @@ def f(): - global data - global idx + global data, idx data = [1, 2, 3] idx = 0 def gen():