F841 treats `except` bindings differently from all other bindings
#16,537 opened on Mar 6, 2025
Description
Summary
The implementation of unused-variable (F841) is split: except bindings are handled separately in bindings.rs, causing false positives. They should be processed consistently with other bindings.
One false positive occurs when the bound variable is not used within the except block but is used outside it. The fix can change program behavior by suppressing a runtime error. The fix could be beneficial, but it should be marked unsafe like other unused bindings’ fixes.
$ cat >f841_1.py <<'# EOF'
def f():
e = None
try:
1 / 0
except ZeroDivisionError as e:
pass
print(e)
f()
# EOF
$ python f841_1.py 2>&1 | tail -n 1
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
$ ruff --isolated check --select F841 f841_1.py --fix
Found 1 error (1 fixed, 0 remaining).
$ cat f841_1.py
def f():
e = None
try:
1 / 0
except ZeroDivisionError:
pass
print(e)
f()
$ python f841_1.py
None
Another false positive occurs when the binding is not within a function.
$ cat >f841_2.py <<'# EOF'
e = None
try:
1 / 0
except ZeroDivisionError as e:
pass
print(e)
# EOF
$ python f841_2.py 2>&1 | tail -n 1
NameError: name 'e' is not defined
$ ruff --isolated check --select F841 f841_2.py --fix
Found 1 error (1 fixed, 0 remaining).
$ cat f841_2.py
e = None
try:
1 / 0
except ZeroDivisionError:
pass
print(e)
$ python f841_2.py
None
There are other false positives, but they are all variations on the same theme. They can probably all be fixed by merging the two F841 implementations.
Version
ruff 0.9.9 (091d0af2a 2025-02-28)