fixhunt

ruff C413

labelled unknown
Runs / 2026-10-17T20-21-26-758Z-0hmi-ruff-C413ruff 0.17.12026-10-17 20:21 UTC8 programs × 2 round(s)16 programs, 9 fired, 6 fixed, 1 findings, 0 rejectedmodel cost $0.02

Analysis Nemotron 3 Super

Flags unnecessary list() or reversed() wrappers around sorted() calls.

Fix: Replaces list(sorted(...)) with sorted(...) and reversed(sorted(...)) with sorted(..., reverse=True).

8 hazards
  • For reversed(sorted(...)), the fix changes behavior when a custom key function is used because reversed() reverses the final list while sorted(..., reverse=True) performs a reverse sort that may reorder equal elements differently.
  • If the sorted call uses a custom comparator via functools.cmp_to_key, reversed() and reverse=True are not equivalent due to differing stability properties.
  • When the iterable passed to sorted is not a list (e.g., a generator), removing list() in list(sorted(...)) changes the return type from list to an iterator, breaking code that expects a list.
  • If the result of list(sorted(...)) is assigned to a variable and later mutated, the fix may affect aliasing if other references expect a list but now get an iterator.
  • Comments inside the list() or reversed() call (e.g., list(sorted(# comment x))) would be dropped by the rewrite as they are not preserved in the simplified expression.
  • If sorted() is called with side-effecting key or reverse expressions (e.g., sorted(x, key=print(len(i)))), the evaluation order of those expressions is unchanged, but the outer call removal could affect debugging or profiling tools that count function invocations.
  • In rare cases where sorted returns a subclass of list (e.g., from a user-defined class overriding __sorted__), removing list() could change the exact type returned, affecting isinstance checks.
  • If the code relies on the fact that list(sorted(...)) creates a new list object (which it does), and sorted(...) also creates a new list, there is no behavioral difference—but if sorted were overridden to return a singleton or cached result, the fix could expose that, though sorted is built-in and not overridable in standard Python.

Findings

programoraclelabeljudgestatussummary
r2-p006fix-changes-behaviorlabelled safereal · highrelatedThe rule documentation explicitly states the fix is safe for `list()` cases because it does not change behavior, but the program demonstrates that removing `li…

Programs

idresulttitlehazard
r1-p001fired, no fixreversed_sorted_key_behavior_changeFor reversed(sorted(...)), the fix changes behavior when a custom key function is used because reversed() reverses the final list while sorted(..., reverse=True) performs a reverse sort that may reord
r1-p002silent prediction offclass_attribute_property_side_effectclass attributes vs instance attributes, descriptors, properties with side effects
r1-p003silent prediction offstar_import_module_side_effects_allstar-imports, module-level side effects, __all__
r1-p004silent prediction offside_effecting_key_expression_evaluation_orderIf sorted() is called with side-effecting key or reverse expressions (e.g., sorted(x, key=print(len(i)))), the evaluation order of those expressions is unchanged, but the outer call removal could affe
r1-p005fix okwalrus_comprehension_scoping_late_bindingwalrus, comprehensions scoping, late binding in closures and lambdas
r1-p006silentbytes_vs_str_encoding_newline_handlingbytes vs str, encoding and newline handling, text vs binary modes
r1-p007silent prediction offmutable_default_arguments_aliasing_after_rewritemutable default arguments and aliasing after a rewrite
r1-p008silent prediction offevaluation_order_side_effects_merged_deletedevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p001silent prediction offshadowed_builtins_redefinitionshadowed builtins and names redefined later in the module
r2-p002fix okmutation_aliasing_after_fixIf the result of list(sorted(...)) is assigned to a variable and later mutated, the fix may affect aliasing if other references expect a list but now get an iterator.
r2-p003fired, no fixcmp_to_key_stability_differenceIf the sorted call uses a custom comparator via functools.cmp_to_key, reversed() and reverse=True are not equivalent due to differing stability properties.
r2-p004fired, no fixcustom_key_reverse_equivalenceFor reversed(sorted(...)), the fix changes behavior when a custom key function is used because reversed() reverses the final list while sorted(..., reverse=True) performs a reverse sort that may reord
r2-p005fix okevaluation_order_side_effects_mergedevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p006fix-changes-behaviorsingleton_sorted_return_exposureIf the code relies on the fact that list(sorted(...)) creates a new list object (which it does), and sorted(...) also creates a new list, there is no behavioral difference—but if sorted were overridde
r2-p007fix ok prediction offcoercion_and_rounding_edgeint/float/bool coercion, Decimal, division and rounding
r2-p008fix okwalrus_lambda_late_bindingwalrus, comprehensions scoping, late binding in closures and lambdas

Sources

r1-p001 · reversed_sorted_key_behavior_change
1data = [('a', 1), ('b', 1), ('c', 2)]
2result_before = list(reversed(sorted(data, key=lambda x: x[1])))
3sorted_direct = sorted(data, key=lambda x: x[1], reverse=True)
4print('Before fix:', result_before)
5print('After fix :', sorted_direct)
6print('Equal?', result_before == sorted_direct)
Before fix: [('c', 2), ('b', 1), ('a', 1)]
After fix : [('c', 2), ('a', 1), ('b', 1)]
Equal? False
r1-p002 · class_attribute_property_side_effect
1class A:
2 _val = 0
3 @property
4 def val(self):
5 A._val += 1
6 return self._x
7 def __init__(self, x):
8 self._x = x
9lst = [A(1), A(2)]
10sorted_lst = sorted(lst, key=lambda i: i.val)
11list_wrapped = list(sorted_lst)
12print('Access count via property:', A._val)
13print('Sorted result:', [(i._x, i.val) for i in list_wrapped])
Access count via property: 2
Sorted result: [(1, 1), (2, 2)]
r1-p003 · star_import_module_side_effects_all
1import sys
2mod = sys.modules['__main__']
3mod.COUNTER = 0
4class Dummy:
5 def __init__(self):
6 mod.COUNTER += 1
7 def __lt__(self, other):
8 return True
9from __main__ import *
10data = [Dummy(), Dummy()]
11sorted_data = sorted(data)
12list_wrapped = list(sorted_data)
13print('COUNTER after list(sorted):', mod.COUNTER)
14print('Length:', len(list_wrapped))
COUNTER after list(sorted): 2
Length: 2
r1-p004 · side_effecting_key_expression_evaluation_order
1calls = []
2def key_fn(x):
3 calls.append(('key', x))
4 return x
5data = [3, 1, 2]
6sorted_once = sorted(data, key=key_fn)
7list_wrapped = list(sorted_once)
8print('Key call log:', calls)
9print('List wrapped:', list_wrapped)
10print('Same as sorted?', list_wrapped == sorted_once)
Key call log: [('key', 3), ('key', 1), ('key', 2)]
List wrapped: [1, 2, 3]
Same as sorted? True
r1-p005 · walrus_comprehension_scoping_late_binding
1funcs = []
2for i in range(3):
3 if (tmp := sorted([i, 0])):
4 f = lambda: tmp
5 funcs.append(f)
6list_wrapped = list(sorted([0]))
7print('Closure results:', [f() for f in funcs])
8print('List wrapped:', list_wrapped)
--- orig.py	2026-10-17 16:21:40.281536202 -0400
+++ fixed.py	2026-10-17 16:21:40.442535376 -0400
@@ -3,6 +3,6 @@
     if (tmp := sorted([i, 0])):
         f = lambda: tmp
         funcs.append(f)
-list_wrapped = list(sorted([0]))
+list_wrapped = sorted([0])
 print('Closure results:', [f() for f in funcs])
 print('List wrapped:', list_wrapped)
Closure results: [[0, 2], [0, 2], [0, 2]]
List wrapped: [0]
r1-p006 · bytes_vs_str_encoding_newline_handling
1import tempfile
2with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
3 f.write('a\nb\nc')
4 fname = f.name
5with open(fname, 'r') as f:
6 lines = f.readlines()
7sorted_lines = sorted(lines)
8print('Sorted lines:', [repr(l) for l in sorted_lines])
9import os
10os.unlink(fname)
Sorted lines: ["'a\\n'", "'b\\n'", "'c'"]
r1-p007 · mutable_default_arguments_aliasing_after_rewrite
1def f(x, lst=[]):
2 lst.append(x)
3 return lst
4data = [3, 1, 2]
5sorted_data = sorted(data, key=f)
6list_wrapped = list(sorted_data)
7print('Default arg state after key calls:', f.__defaults__[0])
8print('Wrapped list:', list_wrapped)
Default arg state after key calls: [3, 1, 2]
Wrapped list: [3, 1, 2]
r1-p008 · evaluation_order_side_effects_merged_deleted
1ref = []
2def side_effect(x):
3 ref.append(x)
4 return -x
5data = [1, 2, 3]
6transformed = [side_effect(x) for x in data]
7sorted_transformed = sorted(transformed)
8list_wrapped = list(sorted_transformed)
9print('Side effect log:', ref)
10print('List wrapped:', list_wrapped)
11print('Sum of wrapped:', sum(list_wrapped))
Side effect log: [1, 2, 3]
List wrapped: [-3, -2, -1]
Sum of wrapped: -6
r2-p001 · shadowed_builtins_redefinition
1sorted = lambda x, **kw: [1]
2result = list(sorted([3, 2]))
3sorted = list
4print(result)
[1]
r2-p002 · mutation_aliasing_after_fix
1x = [3, 1, 2]
2a = b = list(sorted(x))
3a[0] = 99
4print(b)
--- orig.py	2026-10-17 16:21:48.216496016 -0400
+++ fixed.py	2026-10-17 16:21:48.379495202 -0400
@@ -1,4 +1,4 @@
 x = [3, 1, 2]
-a = b = list(sorted(x))
+a = b = sorted(x)
 a[0] = 99
 print(b)
[99, 2, 3]
r2-p003 · cmp_to_key_stability_difference
1from functools import cmp_to_key
2def cmp(a, b):
3 return (a > 0) - (b > 0)
4data = [-1, 1, -2, 2]
5r1 = list(reversed(sorted(data, key=cmp_to_key(cmp))))
6r2 = sorted(data, key=cmp_to_key(cmp), reverse=True)
7print(r1, r2, sep='\n')
[2, 1, -2, -1]
[1, 2, -1, -2]
r2-p004 · custom_key_reverse_equivalence
1data = [(1, 'a'), (2, 'b'), (1, 'c')]
2r1 = list(reversed(sorted(data, key=lambda x: x[0])))
3r2 = sorted(data, key=lambda x: x[0], reverse=True)
4print(r1)
5print(r2)
[(2, 'b'), (1, 'c'), (1, 'a')]
[(2, 'b'), (1, 'a'), (1, 'c')]
r2-p005 · evaluation_order_side_effects_merged
1counter = 0
2def inc():
3 global counter
4 counter += 1
5 return counter
6x = [inc(), inc()]
7r = list(sorted(x))
8counter = 0
9r2 = sorted(x)
10print(r, counter)
11print(r2, counter)
--- orig.py	2026-10-17 16:21:48.822492991 -0400
+++ fixed.py	2026-10-17 16:21:48.996492123 -0400
@@ -4,7 +4,7 @@
     counter += 1
     return counter
 x = [inc(), inc()]
-r = list(sorted(x))
+r = sorted(x)
 counter = 0
 r2 = sorted(x)
 print(r, counter)
[1, 2] 0
[1, 2] 0
r2-p006 · singleton_sorted_return_exposure
1import builtins
2original_sorted = builtins.sorted
3cache = []
4def cached_sorted(*args, **kwargs):
5 if not cache:
6 cache.append(original_sorted(*args, **kwargs))
7 return cache[0]
8builtins.sorted = cached_sorted
9x = [3, 1, 2]
10a = list(sorted(x))
11b = sorted(x)
12print(a is b)
13print(a == b)
--- orig.py	2026-10-17 16:21:49.098491615 -0400
+++ fixed.py	2026-10-17 16:21:49.261490804 -0400
@@ -7,7 +7,7 @@
     return cache[0]
 builtins.sorted = cached_sorted
 x = [3, 1, 2]
-a = list(sorted(x))
+a = sorted(x)
 b = sorted(x)
 print(a is b)
 print(a == b)
False
True
r2-p007 · coercion_and_rounding_edge
1from decimal import Decimal
2x = [Decimal('1.5'), Decimal('1.4')]
3r = list(sorted(x))
4print(type(r[0]), r[0])
--- orig.py	2026-10-17 16:21:49.364490292 -0400
+++ fixed.py	2026-10-17 16:21:49.538489426 -0400
@@ -1,4 +1,4 @@
 from decimal import Decimal
 x = [Decimal('1.5'), Decimal('1.4')]
-r = list(sorted(x))
+r = sorted(x)
 print(type(r[0]), r[0])
<class 'decimal.Decimal'> 1.4
r2-p008 · walrus_lambda_late_binding
1fs = []
2for i in range(2):
3 if (x := list(sorted([i, 0]))) is not None:
4 fs.append(lambda: x)
5print([f() for f in fs])
--- orig.py	2026-10-17 16:21:49.637488934 -0400
+++ fixed.py	2026-10-17 16:21:49.803488108 -0400
@@ -1,5 +1,5 @@
 fs = []
 for i in range(2):
-    if (x := list(sorted([i, 0]))) is not None:
+    if (x := sorted([i, 0])) is not None:
         fs.append(lambda: x)
 print([f() for f in fs])
[[0, 1], [0, 1]]