ruff PLC0207
labelled unknown
Analysis Nemotron 3 Super
The rule flags str.split() or str.rsplit() calls used immediately after indexing [0] or [-1] when no maxsplit=1 argument is present.
Fix: Adds maxsplit=1 as a keyword argument to the split or rsplit call; if positional arguments exist, inserts maxsplit=1 as the second positional argument after the separator (or as the first if no separator is given).
10 hazards
- If the call uses *args, inserting maxsplit=1 may cause a duplicate keyword argument error when *args unpacks a value for maxsplit.
- If the call uses **kwargs, inserting maxsplit=1 may cause a duplicate keyword argument error if **kwargs already contains maxsplit.
- If the separator argument is a variable that evaluates to None, adding maxsplit=1 as a second positional argument changes the call from split(None) to split(None, 1), which alters behavior (split(None) splits on whitespace, split(None, 1) splits on whitespace at most once).
- If the call has no separator and uses split(), adding maxsplit=1 as a positional argument changes split() to split(None, 1), altering behavior from splitting on whitespace to splitting on whitespace at most once.
- If the call uses split('') with an empty separator, adding maxsplit=1 is invalid and raises ValueError because empty separators are not allowed, though the original call would have already raised this error—however, the fix may change the error context or timing if the call was guarded.
- If the call is part of a larger expression where the split result is used elsewhere (e.g., assigned to a variable then indexed), the fix only applies to the indexed use and may miss optimizing other uses, but this is not a behavioral change—however, the rule only triggers on direct indexing, so indirect uses are not fired on.
- If the call appears in a lambda or comprehension and the fix introduces a keyword argument that conflicts with a parameter name in the surrounding scope, it could cause a binding conflict (e.g., split(maxsplit=1) where maxsplit is a lambda parameter).
- If the call is inside a try-except block and the fix changes the exception type (e.g., from TypeError due to duplicate keyword to ValueError), the except handler may not catch it.
- If the call is commented out or dead code, the fix still applies but may be unnecessary; however, this does not change behavior.
- If the call uses keyword arguments for sep or maxsplit already, the fix may incorrectly insert a duplicate (though the rule avoids this by checking for existing maxsplit).
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Keyword-only, positional-only, *args/**kwargs | Testing interaction with function signatures that restrict argument kinds. |
| r1-p002 | fix ok | Empty separator value error propagation | Split on empty string always raises ValueError; fixing should preserve that. |
| r1-p003 | silent | Generator consumption and iterator state | Ensuring iterator is not prematurely consumed by split-side effects. |
| r1-p004 | fix ok prediction off | Dead code elimination safety | Verifying that lint fixes do not alter behavior of unreachable code. |
| r1-p005 | fix ok | Separator None alters splitting semantics | Adding maxsplit=1 as positional arg changes split(None) -> split(None,1). |
| r1-p006 | silent prediction off | Runtime type annotation evaluation | Annotations using string literals or forward refs must not break under fix. |
| r1-p007 | silent | Non-file context manager with write() | Objects mimicking partial file interface should not break split logic. |
| r1-p008 | fix ok | Exception handling and finally clauses | Ensuring rewritten code does not interfere with exception propagation or cleanup. |
| r2-p001 | fix ok | comments and docstrings | comments and docstrings attached to the removed or moved code |
| r2-p002 | silent prediction off | int/float/bool coercion | int/float/bool coercion, Decimal, division and rounding |
| r2-p003 | silent prediction off | bytes vs str | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p004 | silent prediction off | keyword-only, positional-only, *args/**kwargs | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p005 | fired, no fix | *args causing duplicate keyword | If the call uses *args, inserting maxsplit=1 may cause a duplicate keyword argument error when *args unpacks a value for maxsplit. |
| r2-p006 | fix ok | separator None alters semantics | If the separator argument is a variable that evaluates to None, adding maxsplit=1 as a second positional argument changes the call from split(None) to split(None, 1), which alters behavior (split(None |
| r2-p007 | silent | star-imports, module-level side effects | star-imports, module-level side effects, __all__ |
| r2-p008 | silent prediction off | pathlib vs os.path | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
Sources
r1-p001 · Keyword-only, positional-only, *args/**kwargs
1def f(*, sep=None):
2 return 'a b'.rsplit(sep)[0]
3f()
--- orig.py 2026-10-17 16:57:16.243291557 -0400 +++ fixed.py 2026-10-17 16:57:16.413293913 -0400 @@ -1,3 +1,3 @@ def f(*, sep=None): - return 'a b'.rsplit(sep)[0] + return 'a b'.split(sep, maxsplit=1)[0] f()
r1-p002 · Empty separator value error propagation
1try:
2 ''.split('')[0]
3except ValueError as e:
4 print(type(e).__name__, str(e))
--- orig.py 2026-10-17 16:57:16.508295231 -0400 +++ fixed.py 2026-10-17 16:57:16.668297449 -0400 @@ -1,4 +1,4 @@ try: - ''.split('')[0] + ''.split('', maxsplit=1)[0] except ValueError as e: print(type(e).__name__, str(e))
ValueError empty separator
r1-p003 · Generator consumption and iterator state
1def gen():
2 yield 'a b'
3g = gen()
4for _ in g:
5 pass
6print('generator exhausted')
generator exhausted
r1-p004 · Dead code elimination safety
1if False:
2 'x y'.split()[0]
3print('unreachable code ignored')
--- orig.py 2026-10-17 16:57:16.901300679 -0400 +++ fixed.py 2026-10-17 16:57:17.065302952 -0400 @@ -1,3 +1,3 @@ if False: - 'x y'.split()[0] + 'x y'.split(maxsplit=1)[0] print('unreachable code ignored')
unreachable code ignored
r1-p005 · Separator None alters splitting semantics
1s = 'a b c'
2print(s.split(None)[0], s.split(None, 1)[0])
--- orig.py 2026-10-17 16:57:17.162304296 -0400 +++ fixed.py 2026-10-17 16:57:17.322306513 -0400 @@ -1,2 +1,2 @@ s = 'a b c' -print(s.split(None)[0], s.split(None, 1)[0]) +print(s.split(None, maxsplit=1)[0], s.split(None, 1)[0])
a a
r1-p006 · Runtime type annotation evaluation
1def f() -> 'list[str]':
2 return 'x y'.split()
3print(f()[0])
x
r1-p007 · Non-file context manager with write()
1class W:
2 def write(self, s): pass
3 def __enter__(self): return self
4 def __exit__(self, *a): pass
5with W():
6 pass
7print('context manager used')
context manager used
r1-p008 · Exception handling and finally clauses
1try:
2 result = ''.split('')[0]
3except ValueError as e:
4 print('caught:', type(e).__name__)
5finally:
6 print('cleanup')
--- orig.py 2026-10-17 16:57:17.692311639 -0400 +++ fixed.py 2026-10-17 16:57:17.862313995 -0400 @@ -1,5 +1,5 @@ try: - result = ''.split('')[0] + result = ''.split('', maxsplit=1)[0] except ValueError as e: print('caught:', type(e).__name__) finally:
caught: ValueError cleanup
r2-p001 · comments and docstrings
1def f():
2 """Process input."""
3 s = 'a b c'
4 # keep first word
5 first = s.split()[0] # inline comment
6 print(first)
7f()
--- orig.py 2026-10-17 16:57:24.768409450 -0400 +++ fixed.py 2026-10-17 16:57:24.937411781 -0400 @@ -2,6 +2,6 @@ """Process input.""" s = 'a b c' # keep first word - first = s.split()[0] # inline comment + first = s.split(maxsplit=1)[0] # inline comment print(first) f()
a
r2-p002 · int/float/bool coercion
1from decimal import Decimal
2s = '3.14 2.71'
3parts = s.split()
4a = int(parts[0]) # truncate
5b = float(parts[1]) # keep
6c = Decimal(parts[0]) / Decimal(parts[1])
7print(a, b, round(c, 2))
r2-p003 · bytes vs str
1b = b'hello\nworld\n'
2first_line = b.split(b'\n')[0].decode()
3with open('out.txt', 'w') as f:
4 f.write(first_line)
5import os
6os.remove('out.txt')
7print(first_line)
hello
r2-p004 · keyword-only, positional-only, *args/**kwargs
1def splitter(s, sep=None, /, *, maxsplit=-1): # positional-only sep, keyword-only maxsplit
2 return s.split(sep, maxsplit)
3result = splitter('a b c', None)[0] # rule triggers on split() inside
4print(result)
a
r2-p005 · *args causing duplicate keyword
1def call_split(*args):
2 s = 'x y z'
3 return s.split(*args)[0] # rule sees split(*args)[0]
4try:
5 print(call_split()) # -> 'x'
6 print(call_split(' ', 1)) # -> split(' ', 1, 1) -> TypeError
7 print(call_split(maxsplit=1))# -> split(None, maxsplit=1, maxsplit=1) -> TypeError
8except TypeError as e:
9 print('TypeError:', e)
x x TypeError: call_split() got an unexpected keyword argument 'maxsplit'
r2-p006 · separator None alters semantics
1sep = None
2text = ' a b c '
3parts = text.split(sep)[0] # rule triggers: split(None)[0]
4print(repr(parts)) # '' before fix, ' a' after fix
--- orig.py 2026-10-17 16:57:25.836424176 -0400 +++ fixed.py 2026-10-17 16:57:25.997426395 -0400 @@ -1,4 +1,4 @@ sep = None text = ' a b c ' -parts = text.split(sep)[0] # rule triggers: split(None)[0] +parts = text.split(sep, maxsplit=1)[0] # rule triggers: split(None)[0] print(repr(parts)) # '' before fix, ' a' after fix
'a'
r2-p007 · star-imports, module-level side effects
1# This hazard concerns module-level behavior; the rule does not fire here.
2# We include a split but it's not indexed, so expect_trigger=False.
3s = 'hello world'
4print(s.split()) # no indexing -> rule silent
['hello', 'world']
r2-p008 · pathlib vs os.path
1import os
2open('test_file', 'w').close()
3try:
4 p = os.path.abspath('./test_file')
5 name = p.split('/')[-1][:4] # rule triggers on split('/')
6 print(name)
7finally:
8 os.remove('test_file')
test