Skip to content

Preserve the decorated function's identity in timeit - #987

Merged
jeongyoonlee merged 1 commit into
masterfrom
fix/timeit-preserve-signature
Jul 31, 2026
Merged

Preserve the decorated function's identity in timeit#987
jeongyoonlee merged 1 commit into
masterfrom
fix/timeit-preserve-signature

Conversation

@jeongyoonlee

Copy link
Copy Markdown
Collaborator

Proposed changes

timeit (causalml/inference/tree/utils.py) wraps the decorated function in a closure with no functools.wraps, so the decorated method loses its name, docstring and signature:

>>> CausalTreeRegressor.bootstrap_pool.__name__
'wrapped'
>>> inspect.signature(CausalTreeRegressor.bootstrap_pool)
(*args, **kw)

CausalTreeRegressor.bootstrap_pool is the decorator's only user in the package, so that is the method affected today. Anything that introspects it sees the wrong thing — help(), Sphinx autodoc, and scikit-learn's parameter discovery among them.

One line of functools.wraps restores it.

I found this while adding a signature-driven deprecation shim for #854 (#975): the shim could not see bootstrap_pool because its signature read (*args, **kw). The fix stands on its own and has nothing to do with argument order, so it's split out here rather than left buried in that PR. #975 also carries it; whichever lands first, the other merges cleanly.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Tests

Three cases added to tests/test_utils.py:

  • signature, __name__ and __doc__ preservation on a synthetic decorated function
  • CausalTreeRegressor.bootstrap_pool pinned specifically, so a regression is visible where it matters
  • a guard that the decorator still returns the wrapped function's result, since this changes how it handles metadata

The first two fail on master (assert 'wrapped' == 'bootstrap_pool') and pass with the fix. pytest tests/test_causal_trees.py tests/test_utils.py is 39 passed; black --check clean.

`timeit`'s inner closure had no `functools.wraps`, so every decorated method
reported the wrapper's own `(*args, **kw)` signature and the name "wrapped",
and lost its docstring:

    >>> CausalTreeRegressor.bootstrap_pool.__name__
    'wrapped'
    >>> inspect.signature(CausalTreeRegressor.bootstrap_pool)
    (*args, **kw)

`CausalTreeRegressor.bootstrap_pool` is the decorator's only user in the
package, so that is the method affected today. Anything introspecting it sees
the wrong thing -- `help()`, Sphinx autodoc, and scikit-learn's parameter
discovery among them.

Found while adding a signature-driven deprecation shim for #854, which could
not see `bootstrap_pool` for exactly this reason. The fix stands on its own and
is unrelated to that work, so it is split out here.

Three tests: signature/name/docstring preservation on a synthetic decorated
function, `bootstrap_pool` pinned specifically, and a guard that the decorator
still returns the wrapped result. The first two fail on master.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeongyoonlee jeongyoonlee added the bug Something isn't working label Jul 31, 2026
@jeongyoonlee
jeongyoonlee merged commit 28a8b28 into master Jul 31, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant