ruff PLR0203
labelled unknown
Analysis Nemotron 3 Super
Flags assignments where a function defined earlier in the same class body is reassigned via `staticmethod(func)` as a method replacement.
Fix: Replace the assignment `name = staticmethod(name)` with the decorator `@staticmethod` placed directly above the function definition of `name`, preserving the function's name, parameters, body, and decorators, and removing the reassignment line.
11 hazards
- If the function name is used in an annotation within its own body (e.g., `def f(x: f) -> int:`), moving the decorator above may cause a NameError because `f` is not yet defined at annotation evaluation time.
- If the function defines a nested function that references the outer function's name via closure (e.g., `def f(): def g(): return f`), the rewrite breaks the closure because the original function object is replaced by a staticmethod wrapper before the nested function is defined.
- If the function is referenced in a default argument of a later-defined function in the same class body (e.g., `def f(): pass def g(x=f): pass`), the rewrite changes when the staticmethod wrapper is applied, potentially altering the value captured in the default if the wrapper has side effects (though it doesn't, the timing of binding changes relative to other class body statements).
- If the assignment line carries a comment (e.g., `f = staticmethod(f) # workaround`), the rewrite drops that comment entirely, as the fix only preserves comments attached to the function definition.
- If the function is decorated with other decorators (e.g., `@property def f(): pass f = staticmethod(f)`), the rewrite incorrectly places `@staticmethod` above `@property`, reversing decorator order and changing semantics (property vs staticmethod).
- If the function name is deleted after the assignment (e.g., `f = staticmethod(f); del f`), the rewrite leaves the function accessible via its original name, whereas the original code intended to remove it after wrapping.
- If the function is aliased before reassignment (e.g., `orig = f; f = staticmethod(f)`), the rewrite does not update the alias `orig`, which remains bound to the unwrapped function, diverging from the original behavior where `orig` and `f` both pointed to the same unwrapped function before reassignment.
- If the function definition contains a `yield` expression and is used in a generator expression within its own defaults (e.g., `def f(x=(yield 42)): pass; f = staticmethod(f)`), the rewrite changes the timing of when the staticmethod wrapper is applied relative to the first invocation, affecting generator state initialization.
- If the class is defined inside a function or loop and the staticmethod assignment relies on the enclosing scope for nonlocal variables accessed in the function body, the rewrite is safe but may interact poorly with tools that expect the decorator form for static analysis.
- If the function name is the same as a class variable or imported name in the class scope (e.g., `f = 1 def f(): pass f = staticmethod(f)`), the rewrite may confuse linters or readers about which `f` is being decorated, though runtime behavior is preserved.
- If the assignment uses a qualified name like `C.f = staticmethod(f)` inside the class body (invalid syntax but sometimes seen in generated code), the fix does not apply and the rule may not fire, but if it did, the rewrite would be incorrect.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Closure capture broken by premature staticmethod wrapping | If the function defines a nested function that references the outer function's name via closure (e.g., `def f(): def g(): return f`), the rewrite breaks the closure because the original function objec |
| r1-p002 | invalid | Name shadowing confusion between class var and function | If the function name is the same as a class variable or imported name in the class scope (e.g., `f = 1\ndef f(): pass\nf = staticmethod(f)`), the rewrite may confuse linters or readers about which `f` |
| r1-p003 | fix ok | File write with partial data and non-str input after staticmethod rewrite | file modes and truncation, partial writes, non-str data passed to write |
| r1-p004 | invalid | Property descriptor side effects confused with staticmethod | class attributes vs instance attributes, descriptors, properties with side effects |
| r1-p005 | invalid | Walrus and late binding in comprehension capturing reassigned name | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p006 | fix ok | Type annotation evaluated at runtime referencing reassigned function | type annotations evaluated at runtime, typing-only imports |
| r1-p007 | fix ok | Exception in finally block after staticmethod rewrite | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p008 | fix ok | Keyword-only args with default evaluated once, affected by reassignment timing | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p001 | fix ok | Annotation self-reference causes NameError after fix | If the function name is used in an annotation within its own body (e.g., `def f(x: f) -> int:`), moving the decorator above may cause a NameError because `f` is not yet defined at annotation evaluatio |
| r2-p002 | fix ok | Default argument captures pre-wrapper function due to timing shift | If the function is referenced in a default argument of a later-defined function in the same class body (e.g., `def f(): pass def g(x=f): pass`), the rewrite changes when the staticmethod wrapper is ap |
| r2-p003 | fix ok | Context manager-like object with write but no close used in function body | context managers that are not files, objects with write() but no close() |
| r2-p004 | invalid | Decorator order reversed when combining @property and staticmethod reassignment | If the function is decorated with other decorators (e.g., `@property def f(): pass f = staticmethod(f)`), the rewrite incorrectly places `@staticmethod` above `@property`, reversing decorator order |
| r2-p005 | silent | Qualified assignment C.f = staticmethod(f) not handled by fix | If the assignment uses a qualified name like `C.f = staticmethod(f)` inside the class body (invalid syntax but sometimes seen in generated code), the fix does not apply and the rule may not fire, but |
| r2-p006 | fix ok | Nested function accesses nonlocal from enclosing function scope | If the class is defined inside a function or loop and the staticmethod assignment relies on the enclosing scope for nonlocal variables accessed in the function body, the rewrite is safe but may intera |
| r2-p007 | fix ok | Alias to function remains bound to original after reassignment | If the function is aliased before reassignment (e.g., `orig = f; f = staticmethod(f)`), the rewrite does not update the alias `orig`, which remains bound to the unwrapped function, diverging from the |
| r2-p008 | fix ok | Function return value used in truthiness context after reassignment | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
Sources
r1-p001 · Closure capture broken by premature staticmethod wrapping
1class C:
2 def f():
3 def g():
4 return f
5 return g
6 f = staticmethod(f)
7
8c = C()
9g = c.f()
10print(type(g()))
--- orig.py 2026-10-17 17:00:30.979846091 -0400 +++ fixed.py 2026-10-17 17:00:31.379851074 -0400 @@ -1,9 +1,9 @@ class C: + @staticmethod def f(): def g(): return f return g - f = staticmethod(f) c = C() g = c.f()
r1-p002 · Name shadowing confusion between class var and function (generated program is not deterministic)
1class C:
2 f = 99
3 def f():
4 return 'function'
5 f = staticmethod(f)
6
7print(C.f)
8print(C.f())
r1-p003 · File write with partial data and non-str input after staticmethod rewrite
1class C:
2 def log(msg):
3 with open('test.txt', 'w') as fp:
4 fp.write(msg) # expects str
5 fp.write(b'\n') # error: bytes not str
6 log = staticmethod(log)
7
8try:
9 C.log('hello')
10except Exception as e:
11 print(type(e).__name__, str(e))
12finally:
13 import os
14 if os.path.exists('test.txt'):
15 os.remove('test.txt')
--- orig.py 2026-10-17 17:00:31.684854873 -0400 +++ fixed.py 2026-10-17 17:00:31.853856978 -0400 @@ -1,9 +1,9 @@ class C: + @staticmethod def log(msg): with open('test.txt', 'w') as fp: fp.write(msg) # expects str fp.write(b'\n') # error: bytes not str - log = staticmethod(log) try: C.log('hello')
TypeError write() argument must be str, not bytes
r1-p004 · Property descriptor side effects confused with staticmethod (generated program is not deterministic)
1class C:
2 _val = 0
3 @property
4 def score(self):
5 C._val += 1
6 return C._val
7 score = staticmethod(score) # rule triggers: replaces property with staticmethod
8
9c = C()
10try:
11 print(c.score)
12except Exception as e:
13 print(type(e).__name__, str(e))
r1-p005 · Walrus and late binding in comprehension capturing reassigned name (generated program is not deterministic)
1class C:
2 def make():
3 return [lambda: f for _ in range(1) if (f := lambda: 42) is not None]
4 make = staticmethod(make)
5
6funcs = C.make()
7print(funcs[0]())
r1-p006 · Type annotation evaluated at runtime referencing reassigned function
1class C:
2 def f() -> 'return_f()':
3 pass
4 def return_f():
5 return f
6 f = staticmethod(f)
7
8try:
9 C.f()
10except Exception as e:
11 print(type(e).__name__, str(e))
--- orig.py 2026-10-17 17:00:32.143860589 -0400 +++ fixed.py 2026-10-17 17:00:32.309862656 -0400 @@ -1,9 +1,9 @@ class C: + @staticmethod def f() -> 'return_f()': pass def return_f(): return f - f = staticmethod(f) try: C.f()
r1-p007 · Exception in finally block after staticmethod rewrite
1class C:
2 def fails():
3 try:
4 raise ValueError('bad')
5 finally:
6 raise RuntimeError('worse')
7 fails = staticmethod(fails)
8
9try:
10 C.fails()
11except Exception as e:
12 print(type(e).__name__, str(e))
--- orig.py 2026-10-17 17:00:32.417864001 -0400 +++ fixed.py 2026-10-17 17:00:32.583866067 -0400 @@ -1,10 +1,10 @@ class C: + @staticmethod def fails(): try: raise ValueError('bad') finally: raise RuntimeError('worse') - fails = staticmethod(fails) try: C.fails()
RuntimeError worse
r1-p008 · Keyword-only args with default evaluated once, affected by reassignment timing
1class C:
2 default = []
3 def append_val(x, *, lst=default):
4 lst.append(x)
5 return lst
6 append_val = staticmethod(append_val)
7
8print(C.append_val(1))
9print(C.append_val(2))
--- orig.py 2026-10-17 17:00:32.689867387 -0400 +++ fixed.py 2026-10-17 17:00:32.847869355 -0400 @@ -1,9 +1,9 @@ class C: default = [] + @staticmethod def append_val(x, *, lst=default): lst.append(x) return lst - append_val = staticmethod(append_val) print(C.append_val(1)) print(C.append_val(2))
[1] [1, 2]
r2-p001 · Annotation self-reference causes NameError after fix
1class C:
2 def f(x: f) -> int:
3 return x
4 f = staticmethod(f)
5try:
6 c = C()
7 print(c.f(1))
8except Exception as e:
9 print(type(e).__name__, str(e))
--- orig.py 2026-10-17 17:00:38.834943776 -0400 +++ fixed.py 2026-10-17 17:00:39.012945986 -0400 @@ -1,7 +1,7 @@ class C: + @staticmethod def f(x: f) -> int: return x - f = staticmethod(f) try: c = C() print(c.f(1))
1
r2-p002 · Default argument captures pre-wrapper function due to timing shift
1class C:
2 def f():
3 return 9
4 def g(x=f):
5 return x()
6 f = staticmethod(f)
7print(C().g())
--- orig.py 2026-10-17 17:00:39.119947314 -0400 +++ fixed.py 2026-10-17 17:00:39.556952737 -0400 @@ -1,7 +1,7 @@ class C: + @staticmethod def f(): return 9 def g(x=f): return x() - f = staticmethod(f) print(C().g())
r2-p003 · Context manager-like object with write but no close used in function body
1class Writer:
2 def write(self, s):
3 print(s, end='')
4class C:
5 def log(msg):
6 w = Writer()
7 w.write(msg)
8 log = staticmethod(log)
9C.log('hello')
10print('done')
--- orig.py 2026-10-17 17:00:39.777955479 -0400 +++ fixed.py 2026-10-17 17:00:39.946957576 -0400 @@ -2,9 +2,9 @@ def write(self, s): print(s, end='') class C: + @staticmethod def log(msg): w = Writer() w.write(msg) - log = staticmethod(log) C.log('hello') print('done')
hellodone
r2-p004 · Decorator order reversed when combining @property and staticmethod reassignment (generated program is not deterministic)
1class C:
2 @property
3 def f():
4 return 42
5 f = staticmethod(f)
6try:
7 print(C().f)
8except Exception as e:
9 print(type(e).__name__, str(e))
r2-p005 · Qualified assignment C.f = staticmethod(f) not handled by fix
1class C:
2 def f():
3 return 1
4 # This line is invalid syntax; we cannot execute it.
5 # Instead, we show that the rule does not trigger on valid code.
6 pass
7print('no trigger')
no trigger
r2-p006 · Nested function accesses nonlocal from enclosing function scope
1def make_class():
2 x = 10
3 class C:
4 def get():
5 nonlocal x
6 return x
7 get = staticmethod(get)
8 return C
9C = make_class()
10print(C().get())
--- orig.py 2026-10-17 17:00:40.264961521 -0400 +++ fixed.py 2026-10-17 17:00:40.429963567 -0400 @@ -1,10 +1,10 @@ def make_class(): x = 10 class C: + @staticmethod def get(): nonlocal x return x - get = staticmethod(get) return C C = make_class() print(C().get())
10
r2-p007 · Alias to function remains bound to original after reassignment
1class C:
2 def f():
3 return 'original'
4 orig = f
5 f = staticmethod(f)
6print('orig:', orig())
7print('f:', C().f())
8print('same?', orig is C.__dict__['f'])
--- orig.py 2026-10-17 17:00:40.526964770 -0400 +++ fixed.py 2026-10-17 17:00:40.921969669 -0400 @@ -1,8 +1,8 @@ class C: + @staticmethod def f(): return 'original' orig = f - f = staticmethod(f) print('orig:', orig()) print('f:', C().f()) print('same?', orig is C.__dict__['f'])
r2-p008 · Function return value used in truthiness context after reassignment
1class C:
2 def f():
3 return []
4 f = staticmethod(f)
5result = C().f()
6if result:
7 print('truthy')
8else:
9 print('falsy')
10print('len:', len(result))
--- orig.py 2026-10-17 17:00:41.132972286 -0400 +++ fixed.py 2026-10-17 17:00:41.299974356 -0400 @@ -1,7 +1,7 @@ class C: + @staticmethod def f(): return [] - f = staticmethod(f) result = C().f() if result: print('truthy')
falsy len: 0