fixhunt

ruff UP033

labelled unknown
Runs / 2026-10-17T21-19-38-419Z-mi4p-ruff-UP033ruff 0.17.12026-10-17 21:19 UTC8 programs × 2 round(s)16 programs, 9 fired, 9 fixed, 0 findings, 1 rejectedmodel cost $0.0056

Analysis Nemotron 3 Super

Flags usage of functools.lru_cache with maxsize=None, suggesting replacement with functools.cache.

Fix: Replace 'functools.lru_cache(maxsize=None)' with 'functools.cache' while preserving all other decorators, arguments (like typed), whitespace, and syntactic context.

12 hazards
  • Rewriting inside a lambda or comprehension may alter evaluation timing if the decorator is applied dynamically.
  • If 'functools' is imported as an alias (e.g., import functools as ft), the fix may not apply or may produce invalid syntax.
  • When lru_cache is used via getattr or dynamic attribute access (e.g., getattr(functools, 'lru_cache')), the fix cannot be applied and may be missed.
  • If the call to lru_cache includes unpacked kwargs (**kw) where kw might contain 'maxsize', the fix could incorrectly assume maxsize=None is intended.
  • If maxsize=None is passed as a variable (e.g., maxsize=None_var), the fix would incorrectly trigger and replace with cache, changing behavior.
  • In nested decorator expressions like @functools.lru_cache(maxsize=None)(other_arg), the structure may be misinterpreted during rewrite.
  • Comments attached to the lru_cache call (e.g., # type: ignore) may be lost during the rewrite if not carefully preserved.
  • If functools.cache is not available (Python <3.9), the fix would result in a NameError unless the target-version option allows it.
  • When lru_cache is reassigned to a variable and used conditionally, replacing the attribute access may break if the variable is later rebound.
  • If the module defines a local name 'cache' in the same scope, using functools.cache could be shadowed or misinterpreted after rewrite.
  • In string annotations or forward references (e.g., annotations = {'f': 'functools.lru_cache(maxsize=None)'}), the fix should not apply but might be incorrectly triggered.
  • If the decorator is part of a macro or code-generation scheme (e.g., via exec or eval), the static rewrite does not reflect runtime behavior.

Programs

idresulttitlehazard
r1-p001silent prediction offLambda evaluation timing shiftRewriting inside a lambda or comprehension may alter evaluation timing if the decorator is applied dynamically.
r1-p002fix okGenerator consumption differenceGenerators vs lists, iterators consumed once, lazy vs eager evaluation
r1-p003silentDynamic attribute access bypassWhen lru_cache is used via getattr or dynamic attribute access (e.g., getattr(functools, 'lru_cache')), the fix cannot be applied and may be missed.
r1-p004fix okAsync function interactionasync def/await ordering, async generators
r1-p005silent prediction offVariable maxsize false positiveIf maxsize=None is passed as a variable (e.g., maxsize=None_var), the fix would incorrectly trigger and replace with cache, changing behavior.
r1-p006fix okWalrus and closure scopingwalrus, comprehensions scoping, late binding in closures and lambdas
r1-p007silent prediction offUnpacked kwargs interferenceIf the call to lru_cache includes unpacked kwargs (**kw) where kw might contain 'maxsize', the fix could incorrectly assume maxsize=None is intended.
r1-p008fix okClass attribute descriptor interactionclass attributes vs instance attributes, descriptors, properties with side effects
r2-p001fix okKeyword-only, positional-only, *args/**kwargs forwardingkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r2-p002fix okMutable default arguments and aliasing after a rewritemutable default arguments and aliasing after a rewrite
r2-p003silent prediction offUnpacked kwargs interference where kw might contain 'maxsize'If the call to lru_cache includes unpacked kwargs (**kw) where kw might contain 'maxsize', the fix could incorrectly assume maxsize=None is intended.
r2-p004fix okType annotations evaluated at runtime, typing-only importstype annotations evaluated at runtime, typing-only imports
r2-p005silent prediction offEvaluation order and side effects when expressions are merged, duplicated or deletedevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p006fix okWalrus, comprehensions scoping, late binding in closures and lambdaswalrus, comprehensions scoping, late binding in closures and lambdas
r2-p007fix okBytes vs str, encoding and newline handling, text vs binary modesbytes vs str, encoding and newline handling, text vs binary modes
r2-p008invalidClass attributes vs instance attributes, descriptors, properties with side effectsclass attributes vs instance attributes, descriptors, properties with side effects

Sources

r1-p001 · Lambda evaluation timing shift
1import functools
2N = [None]
3decorator_factory = lambda _: functools.lru_cache(maxsize=N[0])
4@decorator_factory(None)
5def f(x): return x * 2
6print('First:', f(5))
7N[0] = 128
8try:
9 print('Second after rebind:', f(5))
10except Exception as e:
11 print(type(e).__name__, str(e))
First: 10
Second after rebind: 10
r1-p002 · Generator consumption difference
1import functools
2@functools.lru_cache(maxsize=None)
3def gen():
4 yield 1
5 yield 2
6 yield 3
7it = gen()
8print('List from first iter:', list(it))
9print('List from second iter:', list(it)) # Should be empty due to caching consuming iterator once
--- orig.py	2026-10-17 17:19:47.679299866 -0400
+++ fixed.py	2026-10-17 17:19:47.858291487 -0400
@@ -1,5 +1,5 @@
 import functools
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def gen():
     yield 1
     yield 2
List from first iter: [1, 2, 3]
List from second iter: []
r1-p003 · Dynamic attribute access bypass
1import functools
2lru = getattr(functools, 'lru_cache')
3@lru(maxsize=None)
4def f(x): return x + 1
5print('Result:', f(5))
Result: 6
r1-p004 · Async function interaction
1import functools
2@functools.lru_cache(maxsize=None)
3async def fetch(n):
4 await __import__('asyncio').sleep(0)
5 return n * 2
6import asyncio
7async def main():
8 r1 = await fetch(3)
9 r2 = await fetch(3)
10 print('Cached async results:', r1, r2)
11asyncio.run(main())
--- orig.py	2026-10-17 17:19:48.095280402 -0400
+++ fixed.py	2026-10-17 17:19:48.533259937 -0400
@@ -1,5 +1,5 @@
 import functools
-@functools.lru_cache(maxsize=None)
+@functools.cache
 async def fetch(n):
     await __import__('asyncio').sleep(0)
     return n * 2
r1-p005 · Variable maxsize false positive
1import functools
2none_val = None
3@functools.lru_cache(maxsize=none_val)
4def f(x): return x + 10
5print('Result with var maxsize:', f(5))
Result with var maxsize: 15
r1-p006 · Walrus and closure scoping
1import functools
2funcs = []
3for i in range(2):
4 if (n := i) == 0:
5 @functools.lru_cache(maxsize=None)
6 def g(x): return x + n
7 funcs.append(g)
8print('Results:', [f(5) for f in funcs]) # Should differ if timing/scoping broken by rewrite
--- orig.py	2026-10-17 17:19:48.893243116 -0400
+++ fixed.py	2026-10-17 17:19:49.060235319 -0400
@@ -2,7 +2,7 @@
 funcs = []
 for i in range(2):
     if (n := i) == 0:
-        @functools.lru_cache(maxsize=None)
+        @functools.cache
         def g(x): return x + n
         funcs.append(g)
 print('Results:', [f(5) for f in funcs])  # Should differ if timing/scoping broken by rewrite
Results: [6]
r1-p007 · Unpacked kwargs interference
1import functools
2kw = {'maxsize': None, 'typed': False}
3@functools.lru_cache(**kw)
4def h(x): return x * 3
5print('Result with unpacked kwargs:', h(4))
Result with unpacked kwargs: 12
r1-p008 · Class attribute descriptor interaction
1import functools
2class A:
3 @functools.lru_cache(maxsize=None)
4 def prop(self):
5 print('Prop called')
6 return 42
7a = A()
8print('First access:', a.prop())
9print('Second access:', a.prop()) # Should not re-print if cached
--- orig.py	2026-10-17 17:19:49.307223798 -0400
+++ fixed.py	2026-10-17 17:19:49.475215962 -0400
@@ -1,6 +1,6 @@
 import functools
 class A:
-    @functools.lru_cache(maxsize=None)
+    @functools.cache
     def prop(self):
         print('Prop called')
         return 42
Prop called
First access: 42
Second access: 42
r2-p001 · Keyword-only, positional-only, *args/**kwargs forwarding
1import functools
2
3def make_func(a, /, b, *, c=42):
4 return a + b + c
5
6@functools.lru_cache(maxsize=None)
7def wrapper(pos_only, /, norm, *, kw_only=99):
8 return make_func(pos_only, norm, kw_only)
9
10print(wrapper(1, 2)) # First call
11print(wrapper(1, 2)) # Cached
12wrapper(1, 2, kw_only=5) # Different kw_only
13print(wrapper(1, 2)) # Should still be cached for kw_only=99
--- orig.py	2026-10-17 17:20:03.735559102 -0400
+++ fixed.py	2026-10-17 17:20:04.130541140 -0400
@@ -3,7 +3,7 @@
 def make_func(a, /, b, *, c=42):
     return a + b + c
 
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def wrapper(pos_only, /, norm, *, kw_only=99):
     return make_func(pos_only, norm, kw_only)
 
r2-p002 · Mutable default arguments and aliasing after a rewrite
1import functools
2
3cache_hits = []
4
5@functools.lru_cache(maxsize=None)
6def func_with_mutable_default(x, lst=[]):
7 lst.append(x)
8 cache_hits.append(len(lst))
9 return lst.copy()
10
11print(func_with_mutable_default(1)) # [1]
12print(func_with_mutable_default(2)) # [1, 2] - cached!
13print(func_with_mutable_default(3)) # [1, 2, 3] - cached!
14print(cache_hits) # Shows if args were re-evaluated
--- orig.py	2026-10-17 17:20:04.344531421 -0400
+++ fixed.py	2026-10-17 17:20:04.517523563 -0400
@@ -2,7 +2,7 @@
 
 cache_hits = []
 
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def func_with_mutable_default(x, lst=[]):
     lst.append(x)
     cache_hits.append(len(lst))
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3]
r2-p003 · Unpacked kwargs interference where kw might contain 'maxsize'
1import functools
2
3def get_kw():
4 return {'maxsize': None, 'typed': True}
5
6@functools.lru_cache(**get_kw())
7def func(x):
8 return x * 2
9
10print(func(5))
11print(func(5)) # Should be cached
12# After fix: becomes @functools.cache(**get_kw()) which is INVALID
13# functools.cache doesn't accept **kwargs -> TypeError at runtime
10
10
r2-p004 · Type annotations evaluated at runtime, typing-only imports
1import functools
2from typing import List, Optional
3
4@functools.lru_cache(maxsize=None)
5def process_data(items: List[int]) -> Optional[int]:
6 if not items:
7 return None
8 return sum(items)
9
10# Trigger annotation evaluation at runtime
11print(process_data.__annotations__)
12result = process_data([1, 2, 3])
13print(result)
14print(process_data([1, 2, 3])) # Cached
--- orig.py	2026-10-17 17:20:04.766512253 -0400
+++ fixed.py	2026-10-17 17:20:05.161494326 -0400
@@ -1,7 +1,7 @@
 import functools
 from typing import List, Optional
 
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def process_data(items: List[int]) -> Optional[int]:
     if not items:
         return None
r2-p005 · Evaluation order and side effects when expressions are merged, duplicated or deleted
1import functools
2
3counter = 0
4
5def get_maxsize():
6 global counter
7 counter += 1
8 print(f'get_maxsize called {counter} times')
9 return None
10
11@functools.lru_cache(maxsize=get_maxsize())
12def compute(x):
13 return x ** 2
14
15print('First call:')
16print(compute(4))
17print('Second call:')
18print(compute(4)) # Should NOT call get_maxsize again
19print(f'Total counter: {counter}')
get_maxsize called 1 times
First call:
16
Second call:
16
Total counter: 1
r2-p006 · Walrus, comprehensions scoping, late binding in closures and lambdas
1import functools
2
3funcs = []
4for i in range(3):
5 if (n := i) >= 0: # walrus: n bound in loop scope
6 @functools.lru_cache(maxsize=None)
7 def make_adder(x, y=n): # late binding: n captured
8 return x + y
9 funcs.append(make_adder)
10
11# All should capture different n due to re-decoration per iteration
12print([f(10) for f in funcs]) # Expect [10, 11, 12] if late binding works
13# After fix: if lru_cache -> cache, but decoration happens same way
14# However, if cache is treated differently in closure creation... unlikely
15# But we test: does re-decoration preserve closure behavior?
--- orig.py	2026-10-17 17:20:05.525477822 -0400
+++ fixed.py	2026-10-17 17:20:05.690470341 -0400
@@ -3,7 +3,7 @@
 funcs = []
 for i in range(3):
     if (n := i) >= 0:  # walrus: n bound in loop scope
-        @functools.lru_cache(maxsize=None)
+        @functools.cache
         def make_adder(x, y=n):  # late binding: n captured
             return x + y
         funcs.append(make_adder)
[10, 11, 12]
r2-p007 · Bytes vs str, encoding and newline handling, text vs binary modes
1import functools
2
3@functools.lru_cache(maxsize=None)
4def encode_string(s, encoding='utf-8'):
5 return s.encode(encoding)
6
7@functools.lru_cache(maxsize=None)
8def decode_bytes(b, encoding='utf-8'):
9 return b.decode(encoding)
10
11text = 'héllö'
12encoded = encode_string(text)
13decoded = decode_bytes(encoded)
14print(f'Original: {repr(text)}')
15print(f'Encoded: {repr(encoded)}')
16print(f'Decoded: {repr(decoded)}')
17print(f'Equal: {text == decoded}')
18# Call again to hit cache
19encoded2 = encode_string(text)
20print(f'Same object: {encoded is encoded2}')
--- orig.py	2026-10-17 17:20:05.796465535 -0400
+++ fixed.py	2026-10-17 17:20:05.968457736 -0400
@@ -1,10 +1,10 @@
 import functools
 
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def encode_string(s, encoding='utf-8'):
     return s.encode(encoding)
 
-@functools.lru_cache(maxsize=None)
+@functools.cache
 def decode_bytes(b, encoding='utf-8'):
     return b.decode(encoding)
 
Original: 'héllö'
Encoded: b'h\xc3\xa9ll\xc3\xb6'
Decoded: 'héllö'
Equal: True
Same object: True
r2-p008 · Class attributes vs instance attributes, descriptors, properties with side effects (generated program is not deterministic)
1import functools
2
3class DataHolder:
4 value = 42
5
6 @functools.lru_cache(maxsize=None)
7 def get_value(self):
8 print('Computing...')
9 return self.value
10
11 @property
12 def cached_prop(self):
13 return self.get_value()
14
15obj = DataHolder
16print(obj.cached_prop) # First: computes
17print(obj.cached_prop) # Second: cached via method
18obj.value = 99
19print(obj.cached_prop) # Still returns 42! Cache ignores instance change
20# After fix: same behavior? But if cache breaks descriptor...