ruff RUF030
labelled unknown
Analysis Nemotron 3 Super
The rule flags assert statements where the second argument is a call to print(), which results in None being passed as the AssertionError message while printing to stdout.
Fix: Replace the print() call in the assert statement's second argument with its string literal argument, removing the print call and using the message directly as the assertion error message.
10 hazards
- The print call may have side effects beyond stdout, such as flushing buffers or triggering I/O errors, which are removed by the fix.
- If print is called with non-string arguments (e.g., print(obj)), the fix incorrectly assumes a string literal, losing the original object's representation.
- When print uses keyword arguments like sep, end, or file, the fix drops these formatting controls, altering output behavior.
- If the print call is part of a larger expression (e.g., print(a) or print(b)), the fix is invalid as the rule only matches simple print calls, but the shape detection might misfire.
- The fix removes any stdout output from the print call, which may be relied upon for logging, debugging, or test output capture.
- In rare cases where print is overridden or mocked (e.g., in unit tests), the fix breaks test expectations by removing the call entirely.
- If the print call appears in a lambda or comprehension within the assert (though unlikely given rule constraints), the fix would incorrectly rewrite it.
- Comments attached to the print call or surrounding whitespace may be lost during the rewrite, depending on the linter's formatting preservation.
- If the assertion message is intended to be None (via print()), the fix changes it to a string, altering the AssertionError's args.
- The fix assumes the print call has exactly one argument; if it has zero or multiple arguments, the rewrite is syntactically invalid or semantically wrong.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | bytes vs str encoding | When fixing `assert condition, print(b'data')`, replacing `print(b'data')` with raw `b'data'` loses the decoding step needed to match downstream string expectations. |
| r1-p002 | fired, no fix | non-string print arg | Replacing `print(obj)` with raw `obj` in `assert cond, print(obj)` changes behavior when obj has custom __str__ or side effects during printing. |
| r1-p003 | fired, no fix | mutable default aliasing | Rewriting `assert cond, print(default_arg)` where default_arg is a mutable default causes shared state across calls if the print is removed and the default is reused. |
| r1-p004 | silent | lambda in assert | If print were inside a lambda in assert (unlikely but testing edge), replacing it would break lambda semantics; though rule doesn't trigger, we simulate risk via side effect in arg. |
| r1-p005 | fired, no fix | string formatting edge cases | Fixing `assert cond, print(f'{x}')` to `f'{x}'` loses the print-side effect and may misrepresent formatting when x has __format__ that does I/O. |
| r1-p006 | fired, no fix | class vs instance attribute | Rewriting `assert cond, print(cls.attr)` where attr is a class attribute accessed via instance may break if fix uses raw value and attr is a property with setter. |
| r1-p007 | fired, no fix | truthiness and bool | When `assert cond, print(msg)` is fixed, if `cond` relies on an object with __bool__ that has side effects, removing print may change evaluation timing in loops. |
| r1-p008 | silent prediction off | keyword-only args | Rewriting `assert cond, print(log(msg, file=f))` where log has keyword-only args may lose the file=sys.stderr intent if fix strips print and assumes string. |
| r2-p001 | fired, no fix | Overridden print mock | In rare cases where print is overridden or mocked (e.g., in unit tests), the fix breaks test expectations by removing the call entirely. |
| r2-p002 | fired, no fix | Custom truthiness | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p003 | silent prediction off | Keyword-only args | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p004 | fired, no fix | Side effects beyond stdout | The print call may have side effects beyond stdout, such as flushing buffers or triggering I/O errors, which are removed by the fix. |
| r2-p005 | silent prediction off | Exceptions and finally | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p006 | fired, no fix | String formatting edge cases | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p007 | fired, no fix | Non-string arguments to print | If print is called with non-string arguments (e.g., print(obj)), the fix incorrectly assumes a string literal, losing the original object's representation. |
| r2-p008 | fired, no fix | Zero or multiple print args | The fix assumes the print call has exactly one argument; if it has zero or multiple arguments, the rewrite is syntactically invalid or semantically wrong. |
Sources
r1-p001 · bytes vs str encoding
1import sys
2buf = bytearray()
3for i in range(2):
4 ok = i == 0
5 try:
6 assert ok, print(b'Hello')
7 except AssertionError as e:
8 msg = str(e.args[0]) if e.args else ''
9 buf.extend(msg.encode('utf-8'))
10 else:
11 buf.extend(b'no assert')
12sys.stdout.write(buf.decode() + '\n')
b'Hello' no assertNone
r1-p002 · non-string print arg
1class Obj:
2 def __init__(self, v): self.v = v
3 def __str__(self): return f'<{self.v}>'
4 def __repr__(self): return self.__str__()
5out = []
6for v in [1, 2]:
7 o = Obj(v)
8 try:
9 assert v < 2, print(o)
10 except AssertionError as e:
11 out.append(str(e))
12 else:
13 out.append('no raise')
14print('|'.join(out))
<2> no raise|None
r1-p003 · mutable default aliasing
1def f(x, lst=[]):
2 lst.append(x)
3 return lst
4results = []
5for i in [1, 2]:
6 try:
7 assert i < 2, print(f(i))
8 except AssertionError as e:
9 results.append(str(e))
10 else:
11 results.append('no raise')
12print('|'.join(results))
[2] no raise|None
r1-p004 · lambda in assert
1calls = []
2g = lambda: calls.append(print('side')) or 42
3try:
4 assert True, g()
5except AssertionError as e:
6 pass
7print(len(calls))
0
r1-p005 · string formatting edge cases
1class Fmt:
2 def __init__(self, v): self.v = v
3 def __format__(self, spec):
4 print(f'FORMATTING {self.v}') # side effect
5 return str(self.v)
6out = []
7for v in ['A', 'B']:
8 f = Fmt(v)
9 try:
10 assert v == 'A', print(f'{f}')
11 except AssertionError as e:
12 out.append(str(e))
13 else:
14 out.append('no raise')
15print('|'.join(out))
FORMATTING B B no raise|None
r1-p006 · class vs instance attribute
1class C:
2 attr = 'class'
3 @property
4 def prop(self): return self._prop
5 @prop.setter
6 def prop(self, v): self._prop = v.upper()
7c = C()
8c.prop = 'set'
9try:
10 assert c.prop == 'SET', print(c.attr)
11except AssertionError as e:
12 print(type(e).__name__, ':', e)
13else:
14 print('no raise')
no raise
r1-p007 · truthiness and bool
1class BoolObj:
2 def __init__(self, flag): self.flag = flag
3 def __bool__(self):
4 print('BOOL_CALLED') # side effect
5 return self.flag
6calls = []
7for i in [0, 1]:
8 o = BoolObj(bool(i))
9 try:
10 assert o, print('fail')
11 except AssertionError as e:
12 calls.append(1)
13 else:
14 calls.append(0)
15print(sum(calls))
BOOL_CALLED fail BOOL_CALLED 1
r1-p008 · keyword-only args
1import sys
2output = []
3def log(m, *, file=sys.stdout):
4 file.write(m + '\n')
5 return m
6for dest in [sys.stdout, sys.stderr]:
7 try:
8 assert False, log('error', file=dest)
9 except AssertionError as e:
10 output.append(str(e))
11 else:
12 output.append('no raise')
13print('|'.join(output))
error error|error
r2-p001 · Overridden print mock
1import sys
2print_calls = []
3def my_print(*a, **k):
4 print_calls.append((a, k))
5 return 'mocked'
6sys.modules['builtins'].__dict__['print'] = my_print
7x = 5
8assert x > 0, print('value', x)
9try:
10 assert False
11except AssertionError as e:
12 print('AssertionError msg:', e)
13print('print_calls:', print_calls)
r2-p002 · Custom truthiness
1class A:
2 def __bool__(self): return False
3 def __len__(self): return 1
4a = A()
5print('bool(a):', bool(a))
6print('len(a):', len(a))
7assert a, print('a is truthy?')
8try:
9 if not a:
10 raise AssertionError('expected')
11except AssertionError as e:
12 print('Caught:', e)
bool(a): False len(a): 1 a is truthy?
r2-p003 · Keyword-only args
1def log(msg, *, file=None):
2 if file:
3 file.write(msg + '\n')
4 return msg
5f = open('tmp.txt', 'w')
6try:
7 x = 1
8 assert x > 0, log('hello', file=f)
9finally:
10 f.close()
11with open('tmp.txt') as f:
12 print('file content:', f.read().strip())
file content:
r2-p004 · Side effects beyond stdout
1import io, sys
2old = sys.stdout
3sys.stdout = io.StringIO()
4try:
5 x = -1
6 assert x >= 0, print('neg', flush=True)
7except AssertionError as e:
8 pass
9finally:
10 sys.stdout = old
11print('stdout buffer:', sys.stdout.getvalue().strip())
r2-p005 · Exceptions and finally
1def bad():
2 raise ValueError('from print')
3x = 0
4try:
5 assert x > 0, bad()
6except AssertionError as ae:
7 print('AssertionError:', ae)
8 if ae.__cause__:
9 print('Cause:', ae.__cause__)
10except Exception as e:
11 print('Other:', type(e).__name__, e)
Other: ValueError from print
r2-p006 · String formatting edge cases
1name = "Bob"
2assert False, print(f'Hi {name!r}')
3try:
4 assert False
5except AssertionError as e:
6 print('Message:', e)
Hi 'Bob'
r2-p007 · Non-string arguments to print
1class Obj:
2 def __str__(self): return 'str'
3 def __repr__(self): return 'Obj()'
4o = Obj()
5assert False, print(o)
6try:
7 assert False
8except AssertionError as e:
9 print('Message:', e)
str
r2-p008 · Zero or multiple print args
1assert False, print('a', 'b')
2try:
3 assert False
4except AssertionError as e:
5 print('Message:', e)
a b