Add the ability for functions to inherit another functions' parameters.

Review Request #14507 — Created July 14, 2025 and updated

Information

typelets
main

Reviewers

One of the big missing gaps in the world of Python's type annotation
support is a way for a function to reference another function's
arguments in a signature. It's common for a function to take *args
and/or **kwargs and pass them somewhere else, but doing this loses all
typing.

The "official" way to deal with this is to effectively make all
arguments keyword-only and to define them in a function and in a
TypedDict, and use that for all wrapping functions. This is good in
many cases, but is not always a suitable approach.

Instead, this change introduces some new utilities for typing the
*args and **kwargs based on another function. The way it works is
through a combination of a decorator and a "ParamsFrom" class.

The "ParamsFrom" classes take in a callable, reference its ParamSpec,
and mixes it into a signature in that class's __call__. This gives us
a new ParamSpec that we can then reference in the decorator.

The decorator takes in the "ParamsFrom" class as a
Callable[TParams, Any]. That gives the decorator access to the
modified signature. It can then return a variation on the decorated
function that uses that signature.

We complete that by fixing up the annotations and signature to use the
right typing for the *args and **kwargs, allowing those to pass
through.

This has some initial startup cost (though minimal) and no runtime cost
when calling functions.

The end result is that any calls to the function will properly type the
parameters going in, based on that signature.

In practice, there are a few constraints to be aware of:

  1. ParamSpec doesn't do well with positional-only parameters. A
    function that has a self and then references positional-only
    parameters seems to reliably break when trying to match the type for
    the decorated function. This is solved with a # type: ignore, and
    does not appear to affect calls to the function.

  2. Function signatures need to be compatible between the
    decorated/signature and the referenced function. There's no going
    from keyword-only to positional-or-keyword, for example.

  3. Pylance/Pyright (and Visual Studio Code) have known issues around
    choosing the wrong docstrings when decorating a function and
    referencing another function as a parameter. This means that if
    hovering over a call to a function in any IDE using these as a basis
    for a LSP, we're going to get the wrong docstring for the function.
    There are many tickets/threads about this, but this is the most
    recent with any promising discussion:
    https://github.com/microsoft/pylance-release/issues/5840

  4. This does nothing useful in PyCharm, which doesn't handle ParamSpec
    fully. This notably is no worse than the behavior functions would
    have by default.

There's thorough documentation within the codebase docs, but a guide to
using this will be coming separately.

This is all very experimental, but types correctly in every major type
checker currently in use except for PyCharm. The hope is that some
wonderful person will design a PEP that makes this entire change
outdated.

Unit tests pass on all supported versions of Python.

Mypy, Pyright, and Pylance report zero typing issues.

Summary ID
Add the ability for functions to inherit another functions' parameters.
One of the big missing gaps in the world of Python's type annotation support is a way for a function to reference another function's arguments in a signature. It's common for a function to take `*args` and/or `**kwargs` and pass them somewhere else, but doing this loses all typing. The "official" way to deal with this is to effectively make all arguments keyword-only and to define them in a function and in a `TypedDict`, and use that for all wrapping functions. This is good in many cases, but is not always a suitable approach. Instead, this change introduces some new utilities for typing the `*args` and `**kwargs` based on another function. The way it works is through a combination of a decorator and a "ParamsFrom" class. The "ParamsFrom" classes take in a callable, reference its `ParamSpec`, and mixes it into a signature in that class's `__call__`. This gives us a new `ParamSpec` that we can then reference in the decorator. The decorator takes in the "ParamsFrom" class as a `Callable[TParams, Any]`. That gives the decorator access to the modified signature. It can then return a variation on the decorated function that uses that signature. We complete that by fixing up the annotations and signature to use the right typing for the `*args` and `**kwargs`, allowing those to pass through. This has some initial startup cost (though minimal) and no runtime cost when calling functions. The end result is that any calls to the function will properly type the parameters going in, based on that signature. In practice, there are a few constraints to be aware of: 1. `ParamSpec` doesn't do well with positional-only parameters. A function that has a `self` and then references positional-only parameters seems to reliably break when trying to match the type for the decorated function. This is solved with a `# type: ignore`, and does not appear to affect calls to the function. 2. Function signatures need to be compatible between the decorated/signature and the referenced function. There's no going from keyword-only to positional-or-keyword, for example. 3. Pylance/Pyright (and Visual Studio Code) have known issues around choosing the wrong docstrings when decorating a function and referencing another function as a parameter. This means that if hovering over a call to a function in any IDE using these as a basis for a LSP, we're going to get the wrong docstring for the function. There are many tickets/threads about this, but this is the most recent with any promising discussion: https://github.com/microsoft/pylance-release/issues/5840 4. This does nothing useful in PyCharm, which doesn't handle `ParamSpec` fully. This notably is no worse than the behavior functions would have by default. There's thorough documentation within the codebase docs, but a guide to using this will be coming separately. This is all very experimental, but types correctly in every major type checker currently in use except for PyCharm. The hope is that some wonderful person will design a PEP that makes this entire change outdated.
9bf005a86732050309da70d001b088d1559a2034
Description From Last Updated

line too long (80 > 79 characters) Column: 80 Error code: E501

reviewbotreviewbot

line too long (80 > 79 characters) Column: 80 Error code: E501

reviewbotreviewbot

line too long (82 > 79 characters) Column: 80 Error code: E501

reviewbotreviewbot

line too long (81 > 79 characters) Column: 80 Error code: E501

reviewbotreviewbot
Checks run (1 failed, 1 succeeded)
flake8 failed.
JSHint passed.

flake8

chipx86
Review request changed
Change Summary:

Fixed line length issues.

Commits:
Summary ID
Add the ability for functions to inherit another functions' parameters.
One of the big missing gaps in the world of Python's type annotation support is a way for a function to reference another function's arguments in a signature. It's common for a function to take `*args` and/or `**kwargs` and pass them somewhere else, but doing this loses all typing. The "official" way to deal with this is to effectively make all arguments keyword-only and to define them in a function and in a `TypedDict`, and use that for all wrapping functions. This is good in many cases, but is not always a suitable approach. Instead, this change introduces some new utilities for typing the `*args` and `**kwargs` based on another function. The way it works is through a combination of a decorator and a "ParamsFrom" class. The "ParamsFrom" classes take in a callable, reference its `ParamSpec`, and mixes it into a signature in that class's `__call__`. This gives us a new `ParamSpec` that we can then reference in the decorator. The decorator takes in the "ParamsFrom" class as a `Callable[TParams, Any]`. That gives the decorator access to the modified signature. It can then return a variation on the decorated function that uses that signature. We complete that by fixing up the annotations and signature to use the right typing for the `*args` and `**kwargs`, allowing those to pass through. This has some initial startup cost (though minimal) and no runtime cost when calling functions. The end result is that any calls to the function will properly type the parameters going in, based on that signature. In practice, there are a few constraints to be aware of: 1. `ParamSpec` doesn't do well with positional-only parameters. A function that has a `self` and then references positional-only parameters seems to reliably break when trying to match the type for the decorated function. This is solved with a `# type: ignore`, and does not appear to affect calls to the function. 2. Function signatures need to be compatible between the decorated/signature and the referenced function. There's no going from keyword-only to positional-or-keyword, for example. 3. Pylance/Pyright (and Visual Studio Code) have known issues around choosing the wrong docstrings when decorating a function and referencing another function as a parameter. This means that if hovering over a call to a function in any IDE using these as a basis for a LSP, we're going to get the wrong docstring for the function. There are many tickets/threads about this, but this is the most recent with any promising discussion: https://github.com/microsoft/pylance-release/issues/5840 4. This does nothing useful in PyCharm, which doesn't handle `ParamSpec` fully. This notably is no worse than the behavior functions would have by default. There's thorough documentation within the codebase docs, but a guide to using this will be coming separately. This is all very experimental, but types correctly in every major type checker currently in use except for PyCharm. The hope is that some wonderful person will design a PEP that makes this entire change outdated.
d3eb4e7816905012687072a7010f05c9c67ac911
Add the ability for functions to inherit another functions' parameters.
One of the big missing gaps in the world of Python's type annotation support is a way for a function to reference another function's arguments in a signature. It's common for a function to take `*args` and/or `**kwargs` and pass them somewhere else, but doing this loses all typing. The "official" way to deal with this is to effectively make all arguments keyword-only and to define them in a function and in a `TypedDict`, and use that for all wrapping functions. This is good in many cases, but is not always a suitable approach. Instead, this change introduces some new utilities for typing the `*args` and `**kwargs` based on another function. The way it works is through a combination of a decorator and a "ParamsFrom" class. The "ParamsFrom" classes take in a callable, reference its `ParamSpec`, and mixes it into a signature in that class's `__call__`. This gives us a new `ParamSpec` that we can then reference in the decorator. The decorator takes in the "ParamsFrom" class as a `Callable[TParams, Any]`. That gives the decorator access to the modified signature. It can then return a variation on the decorated function that uses that signature. We complete that by fixing up the annotations and signature to use the right typing for the `*args` and `**kwargs`, allowing those to pass through. This has some initial startup cost (though minimal) and no runtime cost when calling functions. The end result is that any calls to the function will properly type the parameters going in, based on that signature. In practice, there are a few constraints to be aware of: 1. `ParamSpec` doesn't do well with positional-only parameters. A function that has a `self` and then references positional-only parameters seems to reliably break when trying to match the type for the decorated function. This is solved with a `# type: ignore`, and does not appear to affect calls to the function. 2. Function signatures need to be compatible between the decorated/signature and the referenced function. There's no going from keyword-only to positional-or-keyword, for example. 3. Pylance/Pyright (and Visual Studio Code) have known issues around choosing the wrong docstrings when decorating a function and referencing another function as a parameter. This means that if hovering over a call to a function in any IDE using these as a basis for a LSP, we're going to get the wrong docstring for the function. There are many tickets/threads about this, but this is the most recent with any promising discussion: https://github.com/microsoft/pylance-release/issues/5840 4. This does nothing useful in PyCharm, which doesn't handle `ParamSpec` fully. This notably is no worse than the behavior functions would have by default. There's thorough documentation within the codebase docs, but a guide to using this will be coming separately. This is all very experimental, but types correctly in every major type checker currently in use except for PyCharm. The hope is that some wonderful person will design a PEP that makes this entire change outdated.
9bf005a86732050309da70d001b088d1559a2034

Checks run (2 succeeded)

flake8 passed.
JSHint passed.