ruff FURB180
labelled unknown
Analysis Nemotron 3 Super
Flags explicit metaclass=abc.ABCMeta usage when defining ABCs.
Fix: Replace 'metaclass=abc.ABCMeta' with inheriting from abc.ABC; remove the metaclass keyword argument and add abc.ABC as a base class.
10 hazards
- If the class inherits from typing.Protocol, adding ABC as a base may interfere with Protocol's structural subtyping checks at runtime.
- If the class defines __init_subclass__ that inspects bases, inserting ABC could alter subclass creation behavior.
- If the class relies on the exact tuple of __bases__ for pickling or registration, adding ABC changes that tuple.
- If the class uses a custom metaclass elsewhere in the hierarchy that expects ABCMeta specifically, replacing it with ABC inheritance may break metaclass resolution.
- If the class has a docstring or comment immediately after the metaclass keyword that is tied to formatting, the rewrite may reposition or drop it during AST reformatting.
- If the class is defined inside a function or loop where abc.ABC is not imported in scope, the fix may introduce a NameError unless the import is hoisted.
- If the class uses starargs (**kwargs) in its definition, the fix may reorder or drop those arguments incorrectly during AST transformation.
- If the class is part of a dynamically generated class via type() or similar, the static rewrite does not apply and may produce mismatched behavior.
- If the class defines __classcell__ in a nested scope, adding ABC as a base could interfere with cell binding in Python 3.x.
- If the class is used in a context where abc.ABC is shadowed by a local variable, the fix may bind to the wrong entity.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Context manager without close - object with write() | context managers that are not files, objects with write() but no close() |
| r1-p002 | silent prediction off | Dynamically generated class via type() | If the class is part of a dynamically generated class via type() or similar, the static rewrite does not apply and may produce mismatched behavior. |
| r1-p003 | fired, no fix | Custom metaclass expecting ABCMeta | If the class uses a custom metaclass elsewhere in the hierarchy that expects ABCMeta specifically, replacing it with ABC inheritance may break metaclass resolution. |
| r1-p004 | fix ok | Docstring after metaclass keyword | If the class has a docstring or comment immediately after the metaclass keyword that is tied to formatting, the rewrite may reposition or drop it during AST reformatting. |
| r1-p005 | fix ok | Class inside function without ABC import | If the class is defined inside a function or loop where abc.ABC is not imported in scope, the fix may introduce a NameError unless the import is hoisted. |
| r1-p006 | fix ok | Duplicate keys and dict ordering | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p007 | fix ok | Comments on removed/moved code | comments and docstrings attached to the removed or moved code |
| r1-p008 | fix ok | __classcell__ in nested scope | If the class defines __classcell__ in a nested scope, adding ABC as a base could interfere with cell binding in Python 3.x. |
| r2-p001 | silent prediction off | __bases__ used for pickle registration | Class relies on exact tuple of __bases__ for pickling or registration; adding ABC alters this tuple. |
| r2-p002 | fired, no fix | Runtime-evaluated type annotations with TYPE_CHECKING import | Type annotations evaluated at runtime fail if typing-only imports are not available after fix introduces ABC without hoisting import. |
| r2-p003 | silent prediction off | Mutable default argument aliasing across rewrite | Mutable default arguments in class body cause aliasing issues when ABC is added as base, affecting shared state. |
| r2-p004 | silent prediction off | Class inside function without ABC import in scope | Class defined in function where abc.ABC not imported; fix adds ABC as base causing NameError unless import hoisted. |
| r2-p005 | silent prediction off | Context manager with write() but no close() | Class is context manager-like (has write, no close); fix must not interfere with protocol relying on __bases__. |
| r2-p006 | silent prediction off | Class with **kwargs in definition interacting with fix | Fix may mishandle **kwargs in class definition during AST rewrite, dropping or reordering. |
| r2-p007 | silent prediction off | Descriptor and property with side effects on class rewrite | Class uses descriptor or property with side effects; fix may break binding or evaluation timing. |
| r2-p008 | silent prediction off | Custom __bool__ and __len__ affecting truthiness checks | Class defines __bool__ or __len__; fix must not alter behavior of truthiness or len() after ABC injection. |
Sources
r1-p001 · Context manager without close - object with write()
1import abc
2log = []
3def writer():
4 log.append('write')
5 return type('W', (), {'write': writer, '__enter__': lambda s: None, '__exit__': lambda s, *a: None})()
6with writer() as w:
7 w.write()
8print(log)
r1-p002 · Dynamically generated class via type()
1import abc
2bases = ()
3cls = type('Dynamic', bases, {'metaclass': abc.ABCMeta})
4try:
5 cls()
6except TypeError as e:
7 print('TypeError:', type(e).__name__, str(e))
r1-p003 · Custom metaclass expecting ABCMeta
1import abc
2class MetaABC(type):
3 def __new__(mcs, name, bases, ns):
4 if abc.ABCMeta not in [type(b) for b in bases]:
5 raise RuntimeError('ABCMeta required')
6 return super().__new__(mcs, name, bases, ns)
7class Base(metaclass=MetaABC):
8 pass
9class Derived(Base, metaclass=abc.ABCMeta):
10 pass
11print('ok')
r1-p004 · Docstring after metaclass keyword
1import abc
2class C(metaclass=abc.ABCMeta):
3 '"""doc"""'
4 pass
5print(C.__doc__)
--- orig.py 2026-10-17 17:37:04.754922637 -0400 +++ fixed.py 2026-10-17 17:37:04.915921033 -0400 @@ -1,5 +1,5 @@ import abc -class C(metaclass=abc.ABCMeta): +class C(abc.ABC): '"""doc"""' pass print(C.__doc__)
"""doc"""
r1-p005 · Class inside function without ABC import
1def f():
2 import abc
3 class Local(metaclass=abc.ABCMeta):
4 pass
5 return Local()
6print(type(f()))
--- orig.py 2026-10-17 17:37:05.010920086 -0400 +++ fixed.py 2026-10-17 17:37:05.180918395 -0400 @@ -1,6 +1,6 @@ def f(): import abc - class Local(metaclass=abc.ABCMeta): + class Local(abc.ABC): pass return Local() print(type(f()))
<class '__main__.f.<locals>.Local'>
r1-p006 · Duplicate keys and dict ordering
1import abc
2d = {'a': 1, 'b': 2}
3class C(metaclass=abc.ABCMeta):
4 __hash__ = lambda self: id(self)
5 __eq__ = lambda self, other: self is other
6 def __setattr__(self, k, v):
7 d[k] = v
8obj = C()
9obj.x = 99
10print(d.get('x'))
--- orig.py 2026-10-17 17:37:05.288917320 -0400 +++ fixed.py 2026-10-17 17:37:05.455915658 -0400 @@ -1,6 +1,6 @@ import abc d = {'a': 1, 'b': 2} -class C(metaclass=abc.ABCMeta): +class C(abc.ABC): __hash__ = lambda self: id(self) __eq__ = lambda self, other: self is other def __setattr__(self, k, v):
99
r1-p007 · Comments on removed/moved code
1import abc
2# comment before
3class C(metaclass=abc.ABCMeta): # inline
4 x = 1 # field
5print(C.x)
--- orig.py 2026-10-17 17:37:05.557914643 -0400 +++ fixed.py 2026-10-17 17:37:05.715913071 -0400 @@ -1,5 +1,5 @@ import abc # comment before -class C(metaclass=abc.ABCMeta): # inline +class C(abc.ABC): # inline x = 1 # field print(C.x)
1
r1-p008 · __classcell__ in nested scope
1import abc
2def outer():
3 class Inner(metaclass=abc.ABCMeta):
4 __classcell__ = None
5 def method(self):
6 return 42
7 return Inner()
8print(outer().method())
--- orig.py 2026-10-17 17:37:05.820912026 -0400 +++ fixed.py 2026-10-17 17:37:06.192908326 -0400 @@ -1,6 +1,6 @@ import abc def outer(): - class Inner(metaclass=abc.ABCMeta): + class Inner(abc.ABC): __classcell__ = None def method(self): return 42
r2-p001 · __bases__ used for pickle registration
1import pickle
2REGISTRY = []
3def register(cls):
4 REGISTRY.append((cls.__name__, cls.__bases__))
5 return cls
6@register
7class C(metaclass=abc.ABCMeta):
8 def foo(self): return 42
9print('Before:', [(n, [b.__name__ for b in bs]) for n, bs in REGISTRY])
10data = pickle.dumps(C())
11obj = pickle.loads(data)
12print('After unpickle:', obj.foo())
r2-p002 · Runtime-evaluated type annotations with TYPE_CHECKING import
1from typing import TYPE_CHECKING
2if TYPE_CHECKING:
3 import abc
4class D(metaclass=abc.ABCMeta):
5 x: 'abc.ABC'
6 def f(self): return self.x
7try:
8 d = D()
9 d.x = d
10 print(d.f().__class__.__name__)
11except Exception as e:
12 print(f'{type(e).__name__}: {e}')
r2-p003 · Mutable default argument aliasing across rewrite
1class E(metaclass=abc.ABCMeta):
2 shared = []
3 def __init__(self, value=shared):
4 self.value = value
5 E.shared.append(1)
6e1 = E()
7e2 = E([99])
8print(f'e1.value: {e1.value}, length: {len(e1.value)}')
9print(f'e2.value: {e2.value}, length: {len(e2.value)}')
r2-p004 · Class inside function without ABC import in scope
1def outer():
2 class F(metaclass=abc.ABCMeta):
3 def f(self): return 1
4 return F
5F = outer()
6f = F()
7print(f.f())
r2-p005 · Context manager with write() but no close()
1class Logger(metaclass=abc.ABCMeta):
2 def write(self, msg):
3 self.buf += msg
4 def __enter__(self):
5 self.buf = ''
6 return self
7 def __exit__(self, *exc):
8 print(f'Log: {self.buf}')
9with Logger() as l:
10 l.write('hello')
11 l.write('world')
r2-p006 · Class with **kwargs in definition interacting with fix
1def make_class(**kw):
2 class G(metaclass=abc.ABCMeta, **kw):
3 pass
4 return G
5Klass = make_class(debug=True, verbose=False)
6k = Klass()
7print(f'Klass.__bases__: {[b.__name__ for b in Klass.__bases__]}')
8print(f'Klass.debug: {getattr(Klass, "debug", "missing")}')
r2-p007 · Descriptor and property with side effects on class rewrite
1class VerboseProperty:
2 def __get__(self, obj, owner):
3 print(f'Accessed by {obj}')
4 return 42
5class H(metaclass=abc.ABCMeta):
6 x = VerboseProperty()
7 @property
8 def y(self):
9 print('in y getter')
10 return self._y
11 def __init__(self):
12 self._y = 99
13print('Accessing h.x:')
14h = H()
15val = h.x
16print(f'Got: {val}')
17print('Accessing h.y:')
18val2 = h.y
19print(f'Got: {val2}')
r2-p008 · Custom __bool__ and __len__ affecting truthiness checks
1class Truthy(metaclass=abc.ABCMeta):
2 def __bool__(self):
3 return False
4 def __len__(self):
5 return 5
6 def __init__(self, val):
7 self.val = val
8obj = Truthy(10)
9print(f'bool(obj): {bool(obj)}')
10print(f'len(obj): {len(obj)}')
11print(f'obj is None: {obj is None}')
12print(f'obj == 0: {obj == 0}')
13if not obj:
14 print('obj is falsy')
15else:
16 print('obj is truthy')