ruff TRY201
labelled unknown
Analysis Nemotron 3 Super
Flags unnecessary repetition of caught exception variable when immediately re-raising the same exception.
Fix: Removes the exception variable name from both the except clause and the raise statement, changing 'except X as var: raise var' to 'except X: raise'.
10 hazards
- The exception variable is used after the raise in the same block, so removing it causes an undefined variable error.
- The exception variable is reassigned between the except and raise, so the fix alters which object is raised.
- The exception variable is referenced in a nested function defined between except and raise, capturing the original binding which the fix removes.
- The except clause contains multiple exception types (e.g., 'except (ValueError, TypeError) as exc') and the fix incorrectly drops the variable.
- A comment attached to the 'as exc' token is lost during the rewrite.
- The raise statement is inside a finally block where the variable is referenced from an outer scope, and the fix breaks that reference.
- The exception variable name shadows a global or nonlocal variable, and removing the binding changes later accesses to that variable.
- The except block is empty except for the raise, and removing the variable leaves a pointless 'except:' that may confuse readers but is syntactically valid.
- The code relies on the exception variable being deleted after the block (via implicit cleanup), and altering the binding scope changes deletion timing.
- The raise statement is modified by a decorator or metaclass that inspects the source, and the rewrite changes its behavior.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Multiple exception types - variable dropped | When catching multiple exception types, dropping the variable loses access to the actual exception instance, which may carry different state depending on which type was raised. |
| r1-p002 | fired, no fix | Instance attribute with descriptor side effect | If the exception variable refers to an instance attribute accessed via a descriptor (__get__ has side effects), removing the variable prevents those side effects from occurring during the raise. |
| r1-p003 | fired, no fix | Evaluation order in raised expression | If the exception variable is used in a subexpression with side effects (e.g., function call), removing it changes evaluation order or eliminates the side effect entirely. |
| r1-p004 | fired, no fix prediction off | Pointless bare except after rewrite | Removing the variable from a single-except clause results in 'except:' which catches BaseException (including KeyboardInterrupt, SystemExit), changing exception semantics. |
| r1-p005 | fired, no fix | Decorator inspecting raise statement source | A decorator that parses the source code of the function to detect 'raise <var>' patterns will behave differently if the variable is removed, altering its logic. |
| r1-p006 | silent | Generator state lost by early raise | If the exception variable is used to conditionally resume a generator, removing it breaks the ability to inspect the exception and decide whether to yield or terminate. |
| r1-p007 | fired, no fix | Custom __bool__ affects control flow | If the exception variable is used in a boolean context (e.g., conditional raise), removing it may prevent inspection of a custom __bool__ that affects whether the raise happens. |
| r1-p008 | fired, no fix | Exception in dict key with custom __hash__ | If the exception instance is used as a dict key (relying on __hash__/__eq__), removing the variable prevents it from being used in lookups or storage that depend on its identity or value. |
| r2-p001 | fired, no fix | Mutable default argument retains raised exception across calls | mutable default arguments and aliasing after a rewrite |
| r2-p002 | fired, no fix | Generator expression consumes iterator before raise | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p003 | fired, no fix | Property getter accesses exception variable after raise | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p004 | fired, no fix | Multi-type except drops variable needed after raise | The except clause contains multiple exception types (e.g., 'except (ValueError, TypeError) as exc') and the fix incorrectly drops the variable. |
| r2-p005 | fired, no fix | Exception variable used after raise in same block | The exception variable is used after the raise in the same block, so removing it causes an undefined variable error. |
| r2-p006 | silent prediction off | Finally block references outer-scope exception variable | The raise statement is inside a finally block where the variable is referenced from an outer scope, and the fix breaks that reference. |
| r2-p007 | fired, no fix | Keyword-only args and exception variable capture | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p008 | fired, no fix | Dict ordering affects exception trace | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
Sources
r1-p001 · Multiple exception types - variable dropped
1class MultiExc(Exception):
2 def __init__(self, typ, val):
3 self.typ = typ
4 self.val = val
5 super().__init__(f'{typ}:{val}')
6try:
7 raise MultiExc('TypeError', 42)
8except (ValueError, TypeError) as exc:
9 print(f'Caught {exc.typ}:{exc.val}')
10 raise exc
r1-p002 · Instance attribute with descriptor side effect
1class SideEffectDesc:
2 def __get__(self, obj, objtype):
3 print('DESC_GET')
4 return 42
5class ExcWithDesc(Exception):
6 attr = SideEffectDesc()
7 def __init__(self):
8 super().__init__('desc')
9try:
10 raise ExcWithDesc()
11except Exception as e:
12 print(e.attr) # Triggers DESC_GET
13 raise e
DESC_GET 42
r1-p003 · Evaluation order in raised expression
1state = 0
2def side_effect():
3 global state
4 state += 1
5 return state
6class Exc(Exception):
7 def __init__(self):
8 self.val = side_effect()
9try:
10 raise Exc()
11except Exception as e:
12 print(f'First: {e.val}, Second: {side_effect()}')
13 raise e # Uses e.val after first side_effect()
First: 1, Second: 2
r1-p004 · Pointless bare except after rewrite
1try:
2 raise KeyboardInterrupt('interrupted')
3except Exception as exc: # Would become 'except:' after fix
4 print(f'Caught {type(exc).__name__}')
5 raise exc
r1-p005 · Decorator inspecting raise statement source
1import inspect
2def trace_raise(func):
3 src = inspect.getsource(func)
4 def wrapper():
5 if 'raise exc' in src:
6 print('Trace: raise exc detected')
7 else:
8 print('Trace: raise exc NOT detected')
9 return func()
10 return wrapper
11@trace_raise
12def func():
13 try:
14 raise ValueError('test')
15 except ValueError as exc:
16 print('In handler')
17 raise exc
18func()
Trace: raise exc detected In handler
r1-p006 · Generator state lost by early raise
1def gen():
2 try:
3 yield 1
4 except ValueError as v:
5 if v.args[0] == 'resume':
6 yield 2
7 else:
8 raise # re-raises only if not 'resume'
9 yield 3
10g = gen()
11print(next(g)) # 1
12g.throw(ValueError, 'resume') # Should yield 2
13print(next(g)) # 2
14g.throw(ValueError, 'stop') # Should raise
1 3
r1-p007 · Custom __bool__ affects control flow
1class ConditionalExc(Exception):
2 def __init__(self, flag):
3 self.flag = flag
4 super().__init__()
5 def __bool__(self):
6 return self.flag
7try:
8 raise ConditionalExc(False)
9except ConditionalExc as exc:
10 if exc: # False -> skip raise
11 raise exc
12 print('No raise due to __bool__ False')
No raise due to __bool__ False
r1-p008 · Exception in dict key with custom __hash__
1class HashExc(Exception):
2 def __init__(self, val):
3 self.val = val
4 super().__init__(str(val))
5 def __hash__(self):
6 return hash(self.val)
7 def __eq__(self, other):
8 return isinstance(other, HashExc) and self.val == other.val
9try:
10 exc = HashExc(42)
11 raise exc
12except HashExc as e:
13 d = {e: 'found'}
14 print(d.get(e, 'missing'))
15 raise e
found
r2-p001 · Mutable default argument retains raised exception across calls
1def f(x=[]):
2 try:
3 x.append(1)
4 raise ValueError('bad')
5 except ValueError as exc:
6 raise exc
7print(f())
8print(f())
r2-p002 · Generator expression consumes iterator before raise
1def gen():
2 yield 1
3 yield 2
4g = gen()
5try:
6 next(g)
7 raise RuntimeError('fail')
8except RuntimeError as err:
9 raise err
10print(list(gen()))
r2-p003 · Property getter accesses exception variable after raise
1class C:
2 @property
3 def val(self):
4 return self._v
5 def __init__(self):
6 try:
7 self._v = 5
8 raise ValueError('oops')
9 except ValueError as exc:
10 self._v = exc
11 raise exc
12c = C()
13try:
14 _ = c.val
15except ValueError as e:
16 print('caught:', e)
r2-p004 · Multi-type except drops variable needed after raise
1try:
2 raise ValueError('x')
3except (ValueError, TypeError) as exc:
4 print('exc type:', type(exc).__name__)
5 raise exc
6print('after')
exc type: ValueError
r2-p005 · Exception variable used after raise in same block
1try:
2 raise RuntimeError('fail')
3except RuntimeError as err:
4 raise err
5 print(err)
r2-p006 · Finally block references outer-scope exception variable
1def outer():
2 exc = None
3 try:
4 try:
5 raise ValueError('test')
6 except ValueError as e:
7 exc = e
8 raise
9 finally:
10 if exc is not None:
11 print('finally saw:', exc)
12outer()
finally saw: test
r2-p007 · Keyword-only args and exception variable capture
1def handler(*, ex=None):
2 try:
3 raise KeyError('missing')
4 except KeyError as k:
5 if ex is None:
6 ex = k
7 raise k
8 return ex
9print(handler())
r2-p008 · Dict ordering affects exception trace
1class BadKey:
2 def __init__(self, v):
3 self.v = v
4 def __hash__(self):
5 return hash(self.v)
6 def __eq__(self, other):
7 return self.v == other.v
8try:
9 d = {BadKey(1): 'a', BadKey(2): 'b'}
10 raise ValueError('collision')
11except ValueError as v:
12 d[BadKey(1)] = v
13 raise v
14print(d)