ruff PLR0202
labelled unknown
Analysis Nemotron 3 Super
Flags methods defined as plain functions then assigned via `classmethod(func)` outside the function body.
Fix: Replaces the method definition and subsequent assignment with a single method definition prefixed by the `@classmethod` decorator, preserving the original function name, parameters, and body.
12 hazards
- If the method name is used in an annotation within the same class body before the assignment, moving the decorator may change the binding time and cause a NameError.
- If the class contains a `__prepare__` metaclass method that relies on the order of attribute definitions, inserting the decorator earlier may alter the namespace preparation state.
- If the method is decorated with other decorators (e.g., `@property`) in the original code, the autofix does not preserve them, potentially changing the descriptor behavior.
- If the method assignment is guarded by a runtime condition (e.g., inside an `if` block), the autofix hoists it to unconditional execution, altering when the method becomes a classmethod.
- If the method name is deleted (`del m`) after the assignment but before the end of the class body, the autofix incorrectly retains the method as a classmethod.
- If the method is assigned to an alias (e.g., `n = m`) after the `classmethod` assignment, the autofix breaks the alias because `n` refers to the undecorated function in the original but would refer to the classmethod-descriptor after the fix.
- If the method definition contains a type comment (e.g., `# type: (cls) -> int`) placed after the function, the autofix may discard it during rewrite, altering static type checking behavior.
- If the method is defined in a class executed under `-OO` (optimized mode) where docstrings are omitted, the autofix preserves the docstring but the rewrite timing may interact unexpectedly with stripped constants.
- If the class is nested inside a function and the method uses a closure variable from the outer scope, the autofix does not affect closure capture, but the redefinition order may interfere with late-bound defaults in rare cases.
- If the method name is `__classcell__` (used in Python 3.7+ for class cell handling), rewriting it as a decorated method may interfere with the class creation protocol.
- If the method assignment is preceded by a `from __future__ import` statement that affects execution semantics, the autofix does not account for potential changes in how the function is compiled.
- If the method is part of a mixin intended to be overridden and the assignment relies on the original function being temporarily accessible via the class dict, the autofix may break intermediate access patterns during class construction.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | __prepare__ metaclass depends on attribute definition order | Inserting @classmethod earlier alters namespace preparation state during class creation. |
| r1-p002 | fix ok | Alias to method breaks after autofix | Alias `n = m` after `classmethod` assignment refers to raw function; after fix refers to descriptor. |
| r1-p003 | fix ok | Walrus and closure capture with late binding | Lambda capturing method via walrus in comprehension binds late; autofix changes timing of descriptor availability. |
| r1-p004 | fix ok | -OO mode interacts with docstring preservation | Autofix preserves docstring but -OO strips constants; rewrite may keep dead string affecting memory or timing. |
| r1-p005 | fix ok | Generator expression consumes iterator once | Method used in generator expression; autofix changes when descriptor is available, affecting lazy evaluation. |
| r1-p006 | fix ok | File write with truncation and partial write | Method used in file-writing loop; autofix changes timing, risking incomplete write if interrupted. |
| r1-p007 | fix ok | Star-import and __all__ interaction | Method assigned to __all__; autofix may alter when symbol is available for export. |
| r1-p008 | fix ok | from __future__ import affects function compilation | Future import (e.g., annotations) changes function semantics; autofix doesn't reapply context. |
| r2-p001 | fix ok | Annotation forward reference | If the method name is used in an annotation within the same class body before the assignment, moving the decorator may change the binding time and cause a NameError. |
| r2-p002 | fix ok | Bytes vs str encoding | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p003 | silent prediction off | Conditional assignment hoist | If the method assignment is guarded by a runtime condition (e.g., inside an `if` block), the autofix hoists it to unconditional execution, altering when the method becomes a classmethod. |
| r2-p004 | fix ok | -OO mode docstring interaction | If the method is defined in a class executed under `-OO` (optimized mode) where docstrings are omitted, the autofix preserves the docstring but the rewrite timing may interact unexpectedly with stripp |
| r2-p005 | fix ok | Other decorators lost | If the method is decorated with other decorators (e.g., `@property`) in the original code, the autofix does not preserve them, potentially changing the descriptor behavior. |
| r2-p006 | fix ok | Nested class closure capture | If the class is nested inside a function and the method uses a closure variable from the outer scope, the autofix does not affect closure capture, but the redefinition order may interfere with late-bo |
| r2-p007 | fix ok | Dict ordering duplicate keys | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r2-p008 | fix ok | Mutable default aliasing | mutable default arguments and aliasing after a rewrite |
Sources
r1-p001 · __prepare__ metaclass depends on attribute definition order
1class Meta(type):
2 def __prepare__(name, bases):
3 return {}
4 def __new__(cls, name, bases, ns):
5 order = [k for k in ('a', 'm') if k in ns]
6 ns['order'] = tuple(order)
7 return super().__new__(cls, name, bases, ns)
8
9class C(metaclass=Meta):
10 def m(cls): return 'm'
11 m = classmethod(m)
12 def a(cls): return 'a'
13
14print(C.order)
--- orig.py 2026-10-17 17:00:05.813530631 -0400 +++ fixed.py 2026-10-17 17:00:05.976532687 -0400 @@ -7,8 +7,8 @@ return super().__new__(cls, name, bases, ns) class C(metaclass=Meta): + @classmethod def m(cls): return 'm' - m = classmethod(m) def a(cls): return 'a' print(C.order)
('a', 'm')r1-p002 · Alias to method breaks after autofix
1class C:
2 def m(cls): return 42
3 m = classmethod(m)
4 n = m # alias to original function
5
6try:
7 print('n:', n) # should be function
8 print('n():', n()) # fails: missing cls
9 print('C.m():', C.m()) # works: 42
10 print('C.n():', C.n()) # after fix: would fail if n is descriptor
11
12except Exception as e:
13 print(type(e).__name__, ':', e)
--- orig.py 2026-10-17 17:00:06.074533923 -0400 +++ fixed.py 2026-10-17 17:00:06.236535965 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): return 42 - m = classmethod(m) n = m # alias to original function try:
NameError : name 'n' is not defined
r1-p003 · Walrus and closure capture with late binding
1class C:
2 def m(cls): return cls.__name__
3 m = classmethod(m)
4 fs = [(lambda: m()) for _ in range(1)]
5
6print('fs[0]():', fs[0]()) # Should be 'C'
7# After fix: if m is descriptor at lambda definition, still works
8# But hazard: if class var lookup changes due to timing
--- orig.py 2026-10-17 17:00:06.331537163 -0400 +++ fixed.py 2026-10-17 17:00:06.712541967 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): return cls.__name__ - m = classmethod(m) fs = [(lambda: m()) for _ in range(1)] print('fs[0]():', fs[0]()) # Should be 'C'
r1-p004 · -OO mode interacts with docstring preservation
1class C:
2 def m(cls):
3 '''This is a docstring'''
4 return 42
5 m = classmethod(m)
6
7print('C.m.__doc__:', repr(C.m.__doc__))
8print('C.m():', C.m())
--- orig.py 2026-10-17 17:00:06.936544791 -0400 +++ fixed.py 2026-10-17 17:00:07.103546896 -0400 @@ -1,8 +1,8 @@ class C: + @classmethod def m(cls): '''This is a docstring''' return 42 - m = classmethod(m) print('C.m.__doc__:', repr(C.m.__doc__)) print('C.m():', C.m())
C.m.__doc__: 'This is a docstring' C.m(): 42
r1-p005 · Generator expression consumes iterator once
1class C:
2 def m(cls): return iter([1, 2, 3])
3 m = classmethod(m)
4 g = (next(m()) for _ in range(3))
5
6print('list(g):', list(g)) # Should be [1, 2, 3]
7# After fix: if m is descriptor earlier, same result
8# Hazard: if m looked up too early and state changed
--- orig.py 2026-10-17 17:00:07.206548194 -0400 +++ fixed.py 2026-10-17 17:00:07.593553071 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): return iter([1, 2, 3]) - m = classmethod(m) g = (next(m()) for _ in range(3)) print('list(g):', list(g)) # Should be [1, 2, 3]
r1-p006 · File write with truncation and partial write
1class C:
2 def m(cls): return b'hello'
3 m = classmethod(m)
4
5with open('test.bin', 'wb') as f:
6 for i in range(2):
7 f.write(m()) # writes b'hello' twice
8
9with open('test.bin', 'rb') as f:
10 print('file contents:', f.read())
11import os; os.remove('test.bin')
--- orig.py 2026-10-17 17:00:07.807555767 -0400 +++ fixed.py 2026-10-17 17:00:08.196560668 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): return b'hello' - m = classmethod(m) with open('test.bin', 'wb') as f: for i in range(2):
r1-p007 · Star-import and __all__ interaction
1class C:
2 def m(cls): return 'exported'
3 m = classmethod(m)
4 __all__ = ['m']
5
6# Simulate star import: check what's in __all__
7print('C.__all__:', C.__all__)
8print('hasattr C.m:', hasattr(C, 'm'))
9print('C.m():', C.m())
--- orig.py 2026-10-17 17:00:08.417563452 -0400 +++ fixed.py 2026-10-17 17:00:08.584565555 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): return 'exported' - m = classmethod(m) __all__ = ['m'] # Simulate star import: check what's in __all__
C.__all__: ['m'] hasattr C.m: True C.m(): exported
r1-p008 · from __future__ import affects function compilation
1from __future__ import annotations
2
3class C:
4 def m(cls) -> 'int': # string annotation due to future import
5 return 42
6 m = classmethod(m)
7
8print('C.m.__annotations__:', C.m.__annotations__)
9print('C.m():', C.m())
--- orig.py 2026-10-17 17:00:08.691566903 -0400 +++ fixed.py 2026-10-17 17:00:08.857568994 -0400 @@ -1,9 +1,9 @@ from __future__ import annotations class C: + @classmethod def m(cls) -> 'int': # string annotation due to future import return 42 - m = classmethod(m) print('C.m.__annotations__:', C.m.__annotations__) print('C.m():', C.m())
C.m.__annotations__: {'return': "'int'"}
C.m(): 42r2-p001 · Annotation forward reference
1class C:
2 f: 'm'
3 def m(cls):
4 return 42
5 m = classmethod(m)
6print(C.f.__class__)
--- orig.py 2026-10-17 17:00:14.005633744 -0400 +++ fixed.py 2026-10-17 17:00:14.421638968 -0400 @@ -1,6 +1,6 @@ class C: f: 'm' + @classmethod def m(cls): return 42 - m = classmethod(m) print(C.f.__class__)
r2-p002 · Bytes vs str encoding
1class C:
2 def m(cls):
3 return b'data'
4 m = classmethod(m)
5with open('tmp.bin', 'wb') as f:
6 f.write(C.m())
7with open('tmp.bin', 'rb') as f:
8 print(f.read())
9import os; os.remove('tmp.bin')
--- orig.py 2026-10-17 17:00:14.635641655 -0400 +++ fixed.py 2026-10-17 17:00:14.799643715 -0400 @@ -1,7 +1,7 @@ class C: + @classmethod def m(cls): return b'data' - m = classmethod(m) with open('tmp.bin', 'wb') as f: f.write(C.m()) with open('tmp.bin', 'rb') as f:
b'data'
r2-p003 · Conditional assignment hoist
1flag = True
2class C:
3 def m(cls):
4 return 'ok'
5 if flag:
6 m = classmethod(m)
7print(type(C.m))
<class 'method'>
r2-p004 · -OO mode docstring interaction
1class C:
2 def m(cls):
3 """doc"""
4 return __doc__
5 m = classmethod(m)
6print(C.m())
--- orig.py 2026-10-17 17:00:15.050646867 -0400 +++ fixed.py 2026-10-17 17:00:15.203648787 -0400 @@ -1,6 +1,6 @@ class C: + @classmethod def m(cls): """doc""" return __doc__ - m = classmethod(m) print(C.m())
None
r2-p005 · Other decorators lost
1class C:
2 @property
3 def m(cls):
4 return 'prop'
5 m = classmethod(m)
6try:
7 print(C.m)
8except Exception as e:
9 print(type(e).__name__, e)
--- orig.py 2026-10-17 17:00:15.304650055 -0400 +++ fixed.py 2026-10-17 17:00:15.467652101 -0400 @@ -1,8 +1,8 @@ class C: + @classmethod @property def m(cls): return 'prop' - m = classmethod(m) try: print(C.m) except Exception as e:
<bound method m of <class '__main__.C'>>
r2-p006 · Nested class closure capture
1def make():
2 x = 99
3 class C:
4 def m(cls):
5 return x
6 m = classmethod(m)
7 return C
8C = make()
9print(C.m())
--- orig.py 2026-10-17 17:00:15.562653294 -0400 +++ fixed.py 2026-10-17 17:00:15.723655315 -0400 @@ -1,9 +1,9 @@ def make(): x = 99 class C: + @classmethod def m(cls): return x - m = classmethod(m) return C C = make() print(C.m())
99
r2-p007 · Dict ordering duplicate keys
1class C:
2 def m(cls):
3 return {}
4 m = classmethod(m)
5d = {'a': 1, 'b': 2}
6d[C.m()] = 3
7print(d.get('a'), d.get('b'))
--- orig.py 2026-10-17 17:00:15.826656608 -0400 +++ fixed.py 2026-10-17 17:00:16.223661590 -0400 @@ -1,7 +1,7 @@ class C: + @classmethod def m(cls): return {} - m = classmethod(m) d = {'a': 1, 'b': 2} d[C.m()] = 3 print(d.get('a'), d.get('b'))
r2-p008 · Mutable default aliasing
1class C:
2 def m(cls, lst=[]):
3 lst.append(1)
4 return lst
5 m = classmethod(m)
6print(C.m())
7print(C.m())
--- orig.py 2026-10-17 17:00:16.430664187 -0400 +++ fixed.py 2026-10-17 17:00:16.580666069 -0400 @@ -1,7 +1,7 @@ class C: + @classmethod def m(cls, lst=[]): lst.append(1) return lst - m = classmethod(m) print(C.m()) print(C.m())
[1] [1, 1]