Round #1144
22 May 2026, 17:11· python· claude-sonnet-4-6· r13-clean-hints
- Complexity
- band 2
- ZP / UO / OBS
- 10 / 1 / 15
- Journey weight
- 2152
- Lightshift ΔL
- —
Prompt
Define a Python decorator factory called trace_calls that returns a wrapper preserving the original function via functools.wraps so the __name__ and __doc__ attributes survive. The wrapper should log every call with logging.getLogger before delegating. Stack this decorator above functools.lru_cache with maxsize=128 on a recursive Fibonacci function and confirm the cache hit path still produces the trace entry. Separately, register a functools.singledispatch generic that formats int, list, and dict arguments differently, and decorate each registered overload with trace_calls. Use typing.Callable for the parameter annotation.
Zero-point index (what the engine surfaced)
ZPI-ZPI-0004 | 2026-05-22T17:11:54Z
Define a Python decorator factory called trace_calls that returns a wrapper preserving the original function via functools.wraps so the __name__ and __doc__ attributes survive. The wrapper should log every call with logging.getLogger before delegating. Stack this decorator above functools.lru_cache with maxsize=128 on a recursive Fibonacci function and confirm the cache hit path still produces the trace entry. Separately, register a functools.singledispatch generic that formats int, list, and dict arguments differently, and decorate each registered overload with trace_calls. Use typing.Callable for the parameter annotation.
ZERO POINT — 10 entries (structural invariants)
● hsl(145.55, 100%, 42%) IMemoryCache [contract]
● hsl(209.91, 100%, 28%) ILogger<TCategoryName> [contract]
● hsl(192.14, 100%, 28%) ILoggerFactory [contract]
● hsl(258.53, 100%, 28%) ILoggerProvider [contract]
● hsl(294.97, 100%, 15%) functools.wraps preservation [contract]
● hsl(38.14, 100%, 15%) wrapper identity [contract]
● hsl(111.63, 100%, 15%) decorator semantics [contract]
● hsl(47.02, 100%, 24%) factor theorem [contract]
● hsl(103.84, 100%, 24%) common factor extraction [contract]
● hsl(307.92, 100%, 24%) difference of squares a²-b²=(a-b)(a+b) [contract]
UNOBSERVED — 1 entries (implied truths)
○ hsl(31.79, 100%, 15%) async [feature]
OBSERVED — 15 entries (explicit in request)
◉ hsl(33.84, 100%, 15%) with [feature]
◉ hsl(38.97, 100%, 15%) int [feature]
◉ hsl(52.31, 100%, 15%) dict [feature]
◉ hsl(58.46, 100%, 15%) function [feature]
◉ hsl(157.94, 100%, 15%) __call__ [feature]
◉ hsl(170.25, 100%, 15%) __name__ [feature]
◉ hsl(171.28, 100%, 15%) __doc__ [feature]
◉ hsl(172.30, 100%, 15%) __dict__ [feature]
◉ hsl(238.96, 100%, 15%) overload decorator [feature]
◉ hsl(269.73, 100%, 15%) os path [feature]
◉ hsl(125.54, 100%, 7%) functools wraps [function]
◉ hsl(127.38, 100%, 7%) functools lru_cache [function]
◉ hsl(129.23, 100%, 10%) functools cache [function]
◉ hsl(132.92, 100%, 7%) functools singledispatch [function]
◉ hsl(295.38, 100%, 7%) logging getLogger [function]
COMPLEXITY │ 26 entries │ spread: 264° │ band: HIGH
Baseline response (no ZPI)
```python
"""
Demonstration of:
1. trace_calls decorator factory with functools.wraps
2. Stacking trace_calls above functools.lru_cache on recursive Fibonacci
3. functools.singledispatch with per-overload trace_calls decoration
4. typing.Callable annotations throughout
"""
import functools
import logging
import typing
# ---------------------------------------------------------------------------
# Logging setup
# ---------------------------------------------------------------------------
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
)
# ---------------------------------------------------------------------------
# 1. trace_calls decorator factory
# ---------------------------------------------------------------------------
def trace_calls(func: typing.Callable) -> typing.Callable:
"""
Decorator factory (used directly as a decorator) that logs every call to
*func* via a logger named after the function's module and qualified name,
then delegates to the original implementation.
Using functools.wraps preserves __name__, __doc__, __annotations__, etc.
"""
logger = logging.getLogger(f"{func.__module__}.{func.__qualname__}")
@functools.wraps(func) # ← preserves __name__, __doc__, __wrapped__
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
logger.debug(
"CALL args=%r kwargs=%r",
args,
kwargs,
)
result = func(*args, **kwargs)
logger.debug(
"RETURN value=%r",
result,
)
return result
return wrapper
# ---------------------------------------------------------------------------
# 2. Recursive Fibonacci with lru_cache + trace_calls
#
# Decorator application order (bottom-up):
# ① functools.lru_cache(maxsize=128) – wraps the raw fib function
# ② trace_calls – wraps the cached version
#
# This means trace_calls fires on *every* logical call from the outside,
# including cache hits, because it sits above the cache layer.
# ---------------------------------------------------------------------------
@trace_calls # applied second (outermost)
@functools.lru_cache(maxsize=128) # applied first (innermost)
def fib(n: int) -> int:
"""Return the nth Fibonacci number (0-indexed, recursive)."""
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
# ---------------------------------------------------------------------------
# 3. singledispatch generic formatter
#
# The base implementation handles any type not explicitly registered.
# Each overload is individually wrapped with @trace_calls so the logger
# records which concrete branch was actually invoked.
# ---------------------------------------------------------------------------
@functools.singledispatch
def format_arg(arg: typing.Any) -> str:
"""Generic formatter – falls back to repr() for unknown types."""
return f"<unknown type={type(arg).__name__!r} value={arg!r}>"
@format_arg.register(int)
@trace_calls # trace the int overload
def _format_int(arg: int) -> str:
"""Format an integer argument with its binary and hex representations."""
return (
f"INT decimal={arg} hex={arg:#010x} binary={arg:#010b}"
)
@format_arg.register(list)
@trace_calls # trace the list overload
def _format_list(arg: list) -> str:
"""Format a list argument showing length and each indexed item."""
items = " ".join(f"[{i}]={v!r}" for i, v in enumerate(arg))
return f"LIST len={len(arg)} items=( {items} )"
@format_arg.register(dict)
@trace_calls # trace the dict overload
def _format_dict(arg: dict) -> str:
"""Format a dict argument showing key-value pairs with their types."""
pairs = " ".join(
f"{k!r}({type(k).__name__})→{v!r}({type(v).__name__})"
for k, v in arg.items()
)
return f"DICT len={len(arg)} pairs=( {pairs} )"
# ---------------------------------------------------------------------------
# 4. Verification helpers
# ---------------------------------------------------------------------------
def _verify_metadata() -> None:
"""Confirm that __name__ and __doc__ survive through functools.wraps."""
print("\n=== Metadata preservation ===")
print(f"fib.__name__ : {fib.__name__!r}") # should be 'fib'
print(f"fib.__doc__ : {fib.__doc__!r}")
print(f"_format_int.__name__ : {_format_int.__name__!r}")
print(f"_format_int.__doc__ : {_format_int.__doc__!r}")
print(f"_format_list.__name__ : {_format_list.__name__!r}")
print(f"_format_dict.__name__ : {_format_dict.__name__!r}")
def _verify_fibonacci() -> None:
"""
Call fib twice with the same argument to confirm:
- First call → cache miss → recursive descent logged
- Second call → cache hit → trace_calls still fires (it's above the cache)
"""
print("\n=== Fibonacci (cache-hit path still traced) ===")
fib.cache_clear() # reset so the run is deterministic
print("--- First call: fib(6) ---")
result1 = fib(6)
print(f"fib(6) = {result1}")
print("\n--- Second call: fib(6) [should be a cache hit] ---")
result2 = fib(6)
print(f"fib(6) = {result2}")
info = fib.cache_info()
print(f"\nlru_cache info: {info}")
assert info.hits >= 1, "Expected at least one cache hit"
assert result1 == result2 == 8, f"Unexpected Fibonacci result: {result1}"
def _verify_singledispatch() -> None:
"""Exercise each registered overload and print the formatted strings."""
print("\n=== singledispatch overloads ===")
test_cases: list[typing.Any] = [
42,
[10, "hello", 3.14],
{"name": "Alice", "age": 30, "active": True},
3.14, # falls through to base implementation (no trace_calls there)
]
for value in test_cases:
print(f"\nformat_arg({value!r})")
print(" →", format_arg(value))
# ---------------------------------------------------------------------------
# 5. Main
# ---------------------------------------------------------------------------
if __name__ == "__main__":
_verify_metadata()
_verify_fibonacci()
_verify_singledispatch()
```
---
## Key design decisions explained
### `trace_calls` sits *above* `lru_cache`
```
caller
│
▼
trace_calls wrapper ← fires on EVERY call (including cache hits)
│
▼
lru_cache wrapper ← may return immediately from cache
│
▼
raw fib() ← only reached on a cache miss
```
Because Python applies decorators **bottom-up**, `@functools.lru_cache` runs
first (innermost), then `@trace_calls` wraps the already-cached function.
Every logical call from user code passes through `trace_calls` first, so the
logger fires even when `lru_cache` returns a cached value.
### `functools.wraps` preserves the five dunder attributes
`functools.wraps` copies `__module__`, `__name__`, `__qualname__`,
`__annotations__`, `__doc__`, and sets `__wrapped__` to the original
function, so introspection tools (help(), sphinx, IDEs) see the real
signature rather than the wrapper's `*args, **kwargs`.
### Per-overload `@trace_calls` on `singledispatch`
Each registration is decorated independently:
```python
@format_arg.register
Enhanced response (with ZPI)
```python
"""
trace_calls decorator factory with functools.wraps preservation,
stacked above functools.lru_cache, plus singledispatch formatting.
"""
import logging
import functools
from typing import Callable, Any
# ---------------------------------------------------------------------------
# Logging setup
# ---------------------------------------------------------------------------
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s — %(message)s",
)
# ---------------------------------------------------------------------------
# trace_calls decorator factory
# ---------------------------------------------------------------------------
def trace_calls(func: Callable) -> Callable:
"""
Decorator factory that logs every call to *func* before delegating.
Preserves __name__, __doc__, and all other attributes via functools.wraps.
"""
logger = logging.getLogger(func.__qualname__)
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
logger.debug(
"CALL args=%r kwargs=%r", args, kwargs
)
result = func(*args, **kwargs)
logger.debug(
"RETURN %r → %r", args, result
)
return result
return wrapper
# ---------------------------------------------------------------------------
# Recursive Fibonacci: trace_calls stacked ABOVE lru_cache
#
# Decoration order matters:
# @trace_calls ← outermost: every external call is traced
# @functools.lru_cache ← caches the *traced* wrapper's results
#
# This means:
# • A cache MISS → wrapper fires → real fib() executes recursively.
# • A cache HIT → wrapper fires → lru_cache short-circuits fib(),
# but the trace entry is still produced.
# ---------------------------------------------------------------------------
@trace_calls
@functools.lru_cache(maxsize=128)
def fib(n: int) -> int:
"""Return the nth Fibonacci number (0-indexed)."""
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
# ---------------------------------------------------------------------------
# singledispatch generic with per-overload trace_calls decoration
# ---------------------------------------------------------------------------
@functools.singledispatch
def format_arg(arg: Any) -> str:
"""Format an argument of any type as a string (fallback)."""
return f"<unknown type={type(arg).__name__} value={arg!r}>"
@format_arg.register(int)
@trace_calls
def _(arg: int) -> str:
"""Format an integer: shows its value and square."""
return f"int({arg}) → square={arg ** 2}"
@format_arg.register(list)
@trace_calls
def _(arg: list) -> str:
"""Format a list: shows length and elements."""
return f"list(len={len(arg)}) → {arg!r}"
@format_arg.register(dict)
@trace_calls
def _(arg: dict) -> str:
"""Format a dict: shows keys."""
return f"dict(keys={list(arg.keys())!r})"
# ---------------------------------------------------------------------------
# Verification
# ---------------------------------------------------------------------------
def main() -> None:
separator = "─" * 60
# --- Fibonacci identity checks ---
print(separator)
print(f"fib.__name__ = {fib.__name__!r}") # 'fib', not 'wrapper'
print(f"fib.__doc__ = {fib.__doc__!r}")
print(f"fib.__wrapped__ exists: {hasattr(fib, '__wrapped__')}")
print(separator)
print("First call (cold cache, recursive):")
print(f" fib(10) = {fib(10)}")
print(separator)
print("Second call (cache hit — trace entry still fires):")
print(f" fib(10) = {fib(10)}")
print(separator)
print(f"Cache info: {fib.__wrapped__.cache_info()}") # lru_cache metadata
# --- singledispatch dispatch + trace ---
print(separator)
print("singledispatch overloads:")
for value in (42, [1, 2, 3], {"a": 1, "b": 2}):
print(f" format_arg({value!r})")
result = format_arg(value)
print(f" → {result}")
print()
# Confirm overload __name__ / __doc__ survived wrapping
print(separator)
int_impl = format_arg.dispatch(int)
print(f"int overload __name__ = {int_impl.__name__!r}") # '_'
print(f"int overload __doc__ = {int_impl.__doc__!r}")
if __name__ == "__main__":
main()
```
**How it fits together:**
`trace_calls` calls `logging.getLogger(func.__qualname__)` *once at decoration time*, giving each function its own named logger. `functools.wraps` copies `__wrapped__`, `__name__`, `__doc__`, `__dict__`, and `__module__` from the original onto `wrapper`, so identity is fully preserved.
**Stack order for Fibonacci** is the critical point. `@trace_calls` sits above `@functools.lru_cache`, so the *cached* entity is the raw `fib`; the *traced* entity is the cached result. A second call to `fib(10)` still passes through `wrapper` (producing a log line) before `lru_cache` returns immediately without recursing — confirming the cache-hit path is traced.
`fib.__wrapped__` tunnels through `functools.wraps` to the `lru_cache`-decorated layer, giving access to `.cache_info()` and `.cache_clear()`.
**For `singledispatch`**, each `@format_arg.register` overload is independently wrapped with `trace_calls`. Because `functools.wraps` is applied *after* singledispatch registration, the logger name reflects the original `_` function's `__qualname__` (e.g. `_` inside the module), which you can customise by naming the functions explicitly instead of using `_`.
Grader chain
Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.
V1samba/Meta-Llama-3.3-70B-Instruct
BetterV4qwen2.5-coder-3b-instruct
BetterV5openrouter/qwen/qwen3-235b-a22b-2507
BetterV7qwen2.5-coder-7b-instruct
BetterV8openai/gpt-4.1
SameV9anthropic/claude-opus-4-7
WorseV12openai/gpt-4o
BetterComments
Our grader said what it said. What do you say? Comment as a guest below.
No comments yet. Be the first to say what you make of this round.