ruff RUF077
labelled unknown
Analysis Nemotron 3 Super
Flags methods where the first parameter (receiver) has a default value.
Fix: Removes the default value expression from the receiver parameter (the first parameter of the method definition).
12 hazards
- If the method is called directly via the class (e.g., C.m()) without providing the receiver argument, removing the default causes a TypeError due to missing positional argument.
- If the default value is a mutable object (e.g., self=[]), removing it eliminates shared state across calls, changing behavior for callers who relied on that sharing.
- If the default value has side effects (e.g., self=print('hi') or self=f()), removing it prevents those side effects from occurring at function definition time.
- If the default value is used elsewhere in the method body (e.g., def f(self=None): return self is None), removing it may cause NameError if the parameter is referenced without being passed.
- If the method is overridden in a subclass and the subclass relies on the parent's default for dispatch or behavior, removing the default breaks the override contract.
- If the default value depends on variables from outer scope (e.g., self=x where x is defined later), removing it changes closure capture and may cause NameError if x is not defined when the method is called.
- If the method is used in a context where the receiver is intentionally omitted and the default provides a sentinel (e.g., for optional chaining or mocking), removing it breaks that pattern.
- If the default value is an expression that evaluates to a type or class used in annotations or isinstance checks within the method, removing it alters those checks.
- If the method is part of an API where callers explicitly rely on the ability to omit the receiver (even if unconventional), removing the default constitutes a breaking change.
- If the default value is a forward reference or string annotation (e.g., self="Self"), removing it may affect tools that inspect defaults for refactoring or documentation.
- If the method contains nested functions that close over the receiver parameter and rely on its default value for correctness, removing the default alters the captured value in those closures.
- If the default value is a decorated expression or involves complex operators (e.g., self=a+b), removing it changes evaluation timing and may affect performance or side-effect order.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Bytes vs Str Encoding Hazard | bytes vs str, encoding and newline handling, text vs binary modes |
| r1-p002 | fired, no fix | Async Await Ordering Hazard | async def/await ordering, async generators |
| r1-p003 | invalid | Subclass Override Dispatch Hazard | If the method is overridden in a subclass and the subclass relies on the parent's default for dispatch or behavior, removing the default breaks the override contract. |
| r1-p004 | silent prediction off | Evaluation Order Side Effects Hazard | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r1-p005 | fired, no fix | Direct Class Call Missing Arg Hazard | If the method is called directly via the class (e.g., C.m()) without providing the receiver argument, removing the default causes a TypeError due to missing positional argument. |
| r1-p006 | silent prediction off | Omitted Receiver Sentinel Hazard | If the method is used in a context where the receiver is intentionally omitted and the default provides a sentinel (e.g., for optional chaining or mocking), removing it breaks that pattern. |
| r1-p007 | silent | Star Imports Module Side Effects Hazard | star-imports, module-level side effects, __all__ |
| r1-p008 | silent prediction off | Shadowed Builtins Redefinition Hazard | shadowed builtins and names redefined later in the module |
| r2-p001 | fired, no fix | Type check depends on default receiver value | If the default value is an expression that evaluates to a type or class used in annotations or isinstance checks within the method, removing it alters those checks. |
| r2-p002 | invalid | Forward reference as default affects tooling | If the default value is a forward reference or string annotation (e.g., self="Self"), removing it may affect tools that inspect defaults for refactoring or documentation. |
| r2-p003 | fired, no fix | Receiver default used in method body causes NameError after fix | If the default value is used elsewhere in the method body (e.g., def f(self=None): return self is None), removing it may cause NameError if the parameter is referenced without being passed. |
| r2-p004 | fired, no fix | Exception in default expression affects finally/else chaining | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p005 | fired, no fix | Mutable default receiver causes shared state across calls | If the default value is a mutable object (e.g., self=[]), removing it eliminates shared state across calls, changing behavior for callers who relied on that sharing. |
| r2-p006 | fired, no fix | Generator default vs list: exhaustion behavior differs | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p007 | fired, no fix | Side effect in default expression silenced by fix | If the default value has side effects (e.g., self=print('hi') or self=f()), removing it prevents those side effects from occurring at function definition time. |
| r2-p008 | invalid | Complex expression default changes evaluation timing | If the default value is a decorated expression or involves complex operators (e.g., self=a+b), removing it changes evaluation timing and may affect performance or side-effect order. |
Sources
r1-p001 · Bytes vs Str Encoding Hazard
1def read_data(self=b''):
2 data = self.decode() if isinstance(self, bytes) else self
3 return len(data)
4
5print(read_data(b'hello'))
6try:
7 print(read_data('world'))
8except Exception as e:
9 print(type(e).__name__, str(e))
5 5
r1-p002 · Async Await Ordering Hazard
1class AsyncWorker:
2 async def process(self={}):
3 await asyncio.sleep(0)
4 return 'done'
5
6async def main():
7 w = AsyncWorker()
8 result = await w.process()
9 print(result)
10
11asyncio.run(main())
r1-p003 · Subclass Override Dispatch Hazard (generated program is not deterministic)
1class Base:
2 def handle(self='base'):
3 return f'Base: {self}'
4
5class Sub(Base):
6 def handle(self):
7 return super().handle() + ' -> Sub'
8
9b = Base()
10s = Sub()
11print(b.handle())
12print(s.handle())
r1-p004 · Evaluation Order Side Effects Hazard
1counter = 0
2
3def get_default():
4 global counter
5 counter += 1
6 return counter
7
8def method(self=get_default()):
9 return self
10
11print(method())
12print(method())
13print(f'Counter: {counter}')
1 1 Counter: 1
r1-p005 · Direct Class Call Missing Arg Hazard
1class C:
2 def method(self=42):
3 return self * 2
4
5print(C.method())
6try:
7 C().method()
8except Exception as e:
9 print(type(e).__name__, str(e))
84 TypeError unsupported operand type(s) for *: 'C' and 'int'
r1-p006 · Omitted Receiver Sentinel Hazard
1sentinel = object()
2
3def api_call(self=sentinel):
4 if self is sentinel:
5 return 'default_used'
6 return f'value: {self}'
7
8print(api_call())
9print(api_call('actual'))
default_used value: actual
r1-p007 · Star Imports Module Side Effects Hazard
1__all__ = ['helper']
2
3def helper(self=[]):
4 self.append(1)
5 return self
6
7print(helper())
8print(helper())
[1] [1, 1]
r1-p008 · Shadowed Builtins Redefinition Hazard
1list = lambda x: x
2
3def process(self=list):
4 return self([1, 2, 3])
5
6print(process())
7print(list([4, 5]))
[1, 2, 3] [4, 5]
r2-p001 · Type check depends on default receiver value
1import typing
2T = int
3class C:
4 def m(self=T):
5 if isinstance(self, int):
6 return 1
7 return 0
8print(C().m())
0
r2-p002 · Forward reference as default affects tooling (generated program is not deterministic)
1class D:
2 def f(self="D"):
3 return self
4x = D().f()
5print(type(x).__name__ if isinstance(x, str) else x)
r2-p003 · Receiver default used in method body causes NameError after fix
1class E:
2 def m(self=None):
3 return self is None
4try:
5 print(E().m())
6except Exception as ex:
7 print(f'{type(ex).__name__}: {ex}')
False
r2-p004 · Exception in default expression affects finally/else chaining
1def bad():
2 raise ValueError('bad')
3class F:
4 def m(self=bad()):
5 try:
6 return 1
7 except ValueError:
8 return 2
9 finally:
10 return 3
11try:
12 print(F().m())
13except Exception as ex:
14 print(f'{type(ex).__name__}: {ex}')
r2-p005 · Mutable default receiver causes shared state across calls
1class G:
2 def m(self=[]):
3 self.append(1)
4 return len(self)
5a = G()
6print(a.m(), a.m())
r2-p006 · Generator default vs list: exhaustion behavior differs
1def gen():
2 yield 1
3 yield 2
4class H:
5 def m(self=gen()):
6 try:
7 return next(self)
8 except StopIteration:
9 return 0
10print(H().m(), H().m())
r2-p007 · Side effect in default expression silenced by fix
1logs = []
2def side():
3 logs.append('called')
4 return 0
5class I:
6 def m(self=side()):
7 return len(logs)
8print(I().m(), logs)
1 ['called']
r2-p008 · Complex expression default changes evaluation timing (generated program is not deterministic)
1a = 1
2b = 2
3class J:
4 def m(self=a+b):
5 a = 3
6 b = 4
7 return self
8print(J().m())