ruff UP039
labelled unknown
Analysis Nemotron 3 Super
Flags class definitions with empty parentheses when no base classes are specified.
Fix: Removes the empty parentheses following the class name when no base classes are present, preserving all other tokens and whitespace outside the parentheses range.
10 hazards
- The rewrite may remove inline comments inside the parentheses, such as `class A(): # comment` becoming `class A: # comment`, altering comment placement relative to the class header.
- If the parentheses contain a trailing comma like `class A(),`, the fix would incorrectly produce `class A:,` which is a syntax error due to dangling comma.
- In Python 2, empty parentheses in class definitions could influence metaclass resolution implicitly; removing them might alter behavior in legacy code relying on old-style class semantics.
- The fix does not account for parentheses used in type hints on the class line (e.g., `class A() -> None:`), where removing them breaks the return annotation syntax.
- If the class definition is part of a larger statement via line continuation (e.g., `class A() \ : pass`), the fix may break syntactic structure by removing parentheses needed for line joining.
- When the class header includes decorators (e.g., `@dec\nclass A():`), the fix operates correctly but may be misapplied if parentheses are mistakenly interpreted as part of the decorator expression.
- The fix preserves surrounding whitespace but may alter formatting if the parentheses contained internal whitespace that was significant for alignment or readability in a team's style guide.
- In code generated by tools or macros that rely on the exact token sequence `class A():` for pattern matching or replacement, the rewrite could break tooling expectations.
- If the class is defined in a string or eval context (e.g., `eval('class A(): pass')`), the fix does not apply, but if applied manually, it would change the string content and thus the evaluated behavior.
- The rewrite assumes the parentheses are solely for base classes; however, in rare cases involving experimental syntax or future-language parsers, empty parentheses might be reserved for other meanings, making removal forward-incompatible.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Keyword-only, positional-only, args/kwargs, defaults | Empty parentheses after class name define default argument evaluation scope; removing them changes when mutable defaults are evaluated. |
| r1-p002 | fix ok | Exceptions raised inside rewritten code, finally/else, chaining | Class body execution affects exception state; removing parentheses may alter __init_subclass__ timing and exception propagation. |
| r1-p003 | fix ok | Type annotations evaluated at runtime, typing-only imports | Annotated class variables with forward references rely on __annotations__ dict; class body execution order affects availability. |
| r1-p004 | fix ok | Generators vs lists, iterators consumed once, lazy/eager | Class body contains a generator expression; removing parentheses changes when it's evaluated (eager vs lazy) and whether it's exhausted. |
| r1-p005 | fix ok | Class attributes vs instance attributes, descriptors, properties | Property with side effect in class body; removal changes when property is accessed during class creation vs instantiation. |
| r1-p006 | fix ok | Pathlib vs os.path behavior on relative paths, symlinks, missing files | Class attribute uses Path() touching filesystem; removal changes when path is resolved (import time vs runtime). |
| r1-p007 | fix ok | Context managers that are not files, objects with write() but no close() | Class body uses a custom context manager with side effects; removal changes when __enter__/__exit__ run. |
| r1-p008 | fix ok prediction off | Rewrite assumes parentheses are solely for base classes; rare experimental/future syntax | Empty parentheses might be reserved for future syntax (e.g., class parameters); removing them breaks forward compatibility. |
| r2-p001 | fix ok | Coercion with Decimal | int/float/bool coercion, Decimal, division and rounding |
| r2-p002 | fix ok | Star Import Side Effects | star-imports, module-level side effects, __all__ |
| r2-p003 | silent | Legacy Metaclass Resolution | In Python 2, empty parentheses in class definitions could influence metaclass resolution implicitly; removing them might alter behavior in legacy code relying on old-style class semantics. |
| r2-p004 | fix ok | Evaluation Order Merged Expressions | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r2-p005 | invalid | Type Hint Return Annotation | The fix does not account for parentheses used in type hints on the class line (e.g., `class A() -> None:`), where removing them breaks the return annotation syntax. |
| r2-p006 | fix ok | Future Syntax Reserve | The rewrite assumes the parentheses are solely for base classes; however, in rare cases involving experimental syntax or future-language parsers, empty parentheses might be reserved for other meanings |
| r2-p007 | fix ok | Tooling Token Match | In code generated by tools or macros that rely on the exact token sequence `class A():` for pattern matching or replacement, the rewrite could break tooling expectations. |
| r2-p008 | fix ok | File Write Truncation Timing | file modes and truncation, partial writes, non-str data passed to write |
Sources
r1-p001 · Keyword-only, positional-only, args/kwargs, defaults
1def f(x=[]):
2 x.append(1)
3 return x
4class A():
5 b = f()
6print(A.b)
7A2 = A()
8print(f())
--- orig.py 2026-10-17 17:25:19.136502057 -0400 +++ fixed.py 2026-10-17 17:25:19.309497464 -0400 @@ -1,7 +1,7 @@ def f(x=[]): x.append(1) return x -class A(): +class A: b = f() print(A.b) A2 = A()
[1] [1, 1]
r1-p002 · Exceptions raised inside rewritten code, finally/else, chaining
1class Base:
2 def __init_subclass__(cls):
3 raise ValueError('bad')
4try:
5 class C():
6 pass
7except ValueError as e:
8 print('caught:', e)
9else:
10 print('no ex')
11finally:
12 print('finally')
--- orig.py 2026-10-17 17:25:19.413494704 -0400 +++ fixed.py 2026-10-17 17:25:19.573490456 -0400 @@ -2,7 +2,7 @@ def __init_subclass__(cls): raise ValueError('bad') try: - class C(): + class C: pass except ValueError as e: print('caught:', e)
no ex finally
r1-p003 · Type annotations evaluated at runtime, typing-only imports
1class A():
2 x: 'B' = None
3class B: pass
4print(A.__annotations__['x'])
5print(B)
--- orig.py 2026-10-17 17:25:19.682487562 -0400 +++ fixed.py 2026-10-17 17:25:19.859482864 -0400 @@ -1,4 +1,4 @@ -class A(): +class A: x: 'B' = None class B: pass print(A.__annotations__['x'])
B <class '__main__.B'>
r1-p004 · Generators vs lists, iterators consumed once, lazy/eager
1gen = (i for i in [1,2,3])
2class A():
3 y = list(gen)
4print('gen exhausted?', list(gen))
5print('A.y:', A.y)
--- orig.py 2026-10-17 17:25:19.971479890 -0400 +++ fixed.py 2026-10-17 17:25:20.136475516 -0400 @@ -1,5 +1,5 @@ gen = (i for i in [1,2,3]) -class A(): +class A: y = list(gen) print('gen exhausted?', list(gen)) print('A.y:', A.y)
gen exhausted? [] A.y: [1, 2, 3]
r1-p005 · Class attributes vs instance attributes, descriptors, properties
1class A():
2 @property
3 def x(self):
4 print('prop accessed')
5 return 1
6print('before inst')
7a = A()
8print(a.x)
--- orig.py 2026-10-17 17:25:20.231472999 -0400 +++ fixed.py 2026-10-17 17:25:20.405468387 -0400 @@ -1,4 +1,4 @@ -class A(): +class A: @property def x(self): print('prop accessed')
before inst prop accessed 1
r1-p006 · Pathlib vs os.path behavior on relative paths, symlinks, missing files
1import pathlib
2class A():
3 p = pathlib.Path('tmp.txt')
4 p.write_text('hi')
5print('exists:', A.p.exists())
6print('content:', A.p.read_text())
7A.p.unlink()
--- orig.py 2026-10-17 17:25:20.509465631 -0400 +++ fixed.py 2026-10-17 17:25:20.709460330 -0400 @@ -1,5 +1,5 @@ import pathlib -class A(): +class A: p = pathlib.Path('tmp.txt') p.write_text('hi') print('exists:', A.p.exists())
exists: True content: hi
r1-p007 · Context managers that are not files, objects with write() but no close()
1class CM:
2 def __enter__(self):
3 print('enter')
4 return self
5 def __exit__(self, *exc):
6 print('exit')
7class A():
8 with CM():
9 pass
10print('done')
--- orig.py 2026-10-17 17:25:20.834457017 -0400 +++ fixed.py 2026-10-17 17:25:20.986452988 -0400 @@ -4,7 +4,7 @@ return self def __exit__(self, *exc): print('exit') -class A(): +class A: with CM(): pass print('done')
enter exit done
r1-p008 · Rewrite assumes parentheses are solely for base classes; rare experimental/future syntax
1# This is a placeholder for future syntax
2# Currently, class A(): is valid and equivalent to class A:
3# If future Python uses () for class params, removing them would break
4# For now, we show equivalence
5class A():
6 x = 1
7class B:
8 x = 1
9print('A.x:', A.x)
10print('B.x:', B.x)
11print('Equal:', A.x == B.x)
--- orig.py 2026-10-17 17:25:21.089450262 -0400 +++ fixed.py 2026-10-17 17:25:21.254445896 -0400 @@ -2,7 +2,7 @@ # Currently, class A(): is valid and equivalent to class A: # If future Python uses () for class params, removing them would break # For now, we show equivalence -class A(): +class A: x = 1 class B: x = 1
A.x: 1 B.x: 1 Equal: True
r2-p001 · Coercion with Decimal
1class X():
2 a = float(int(Decimal('5')) // 2)
3
4from decimal import Decimal
5print(X.a)
6del X
--- orig.py 2026-10-17 17:25:25.623330658 -0400 +++ fixed.py 2026-10-17 17:25:26.059319199 -0400 @@ -1,4 +1,4 @@ -class X(): +class X: a = float(int(Decimal('5')) // 2) from decimal import Decimal
r2-p002 · Star Import Side Effects
1__all__ = ['flag']
2flag = 0
3
4class Y():
5 global flag
6 flag += 1
7
8from __main__ import *
9print(flag)
--- orig.py 2026-10-17 17:25:26.279313425 -0400 +++ fixed.py 2026-10-17 17:25:26.448308989 -0400 @@ -1,7 +1,7 @@ __all__ = ['flag'] flag = 0 -class Y(): +class Y: global flag flag += 1
1
r2-p003 · Legacy Metaclass Resolution
1# Hazard refers to Python 2 behavior; this version intentionally avoids triggering the rule.
2# Using explicit inheritance to avoid being a candidate for UP039 while still illustrating concept.
3try:
4 class Z(object): pass
5 print(type(Z))
6except Exception as e:
7 print(f'{type(e).__name__}: {e}')
<class 'type'>
r2-p004 · Evaluation Order Merged Expressions
1seq = []
2def log(v): seq.append(v); return v
3
4class A():
5 x = log(1)
6 y = log(2)
7
8print(seq)
9print(A.x, A.y)
10del A
--- orig.py 2026-10-17 17:25:26.682302848 -0400 +++ fixed.py 2026-10-17 17:25:26.846298544 -0400 @@ -1,7 +1,7 @@ seq = [] def log(v): seq.append(v); return v -class A(): +class A: x = log(1) y = log(2)
[1, 2] 1 2
r2-p005 · Type Hint Return Annotation (generated program does not parse: Traceback (most recent call last):)
1class B() -> str:
2 return 'test'
3
4print(B.__annotations__)
5try:
6 B()
7except TypeError as e:
8 print(f'{type(e).__name__}: {e}')
9del B
r2-p006 · Future Syntax Reserve
1# Placeholder for potential future syntax: class params
2# Though not valid now, simulating token sensitivity
3class C():
4 __params__ = ()
5
6print(hasattr(C, '__params__'))
7del C
--- orig.py 2026-10-17 17:25:27.085292275 -0400 +++ fixed.py 2026-10-17 17:25:27.252287899 -0400 @@ -1,6 +1,6 @@ # Placeholder for potential future syntax: class params # Though not valid now, simulating token sensitivity -class C(): +class C: __params__ = () print(hasattr(C, '__params__'))
True
r2-p007 · Tooling Token Match
1token_seq = 'class D():\n x = 1'
2source = ''
3class D():
4 x = 1
5 source = token_seq
6
7print(source == token_seq)
8del D
--- orig.py 2026-10-17 17:25:27.353285252 -0400 +++ fixed.py 2026-10-17 17:25:27.521280850 -0400 @@ -1,6 +1,6 @@ token_seq = 'class D():\n x = 1' source = '' -class D(): +class D: x = 1 source = token_seq
False
r2-p008 · File Write Truncation Timing
1class E():
2 f = open('tmp.txt', 'w')
3 f.write('early')
4 f.close()
5
6with open('tmp.txt', 'r') as f:
7 print(f.read())
8import os; os.remove('tmp.txt')
9del E
--- orig.py 2026-10-17 17:25:27.616278361 -0400 +++ fixed.py 2026-10-17 17:25:27.778274116 -0400 @@ -1,4 +1,4 @@ -class E(): +class E: f = open('tmp.txt', 'w') f.write('early') f.close()
early