如何将已有函数的签名复用为包装器函数的*args、**kwargs及返回值的类型标注?
Great question! When you need to wrap an existing function (that you can't modify) and want your wrapper to mirror its exact parameter types and return type, Python's modern typing tools have a clean solution for this—using ParamSpec and TypeVar. Let's break this down step by step.
First, let's ground this in your example scenario. Let's assume our pre-typed log function looks something like this (I'll add concrete types for clarity):
from typing import Any def log(level: str, *messages: str, **extra: Any) -> None: """A pre-existing log function with full type annotations.""" print(f"[{level}]", *messages, extra)
Now, to create a debug wrapper that perfectly inherits log's parameter and return types, here's the correct approach:
from typing import ParamSpec, TypeVar, Callable # Define a parameter spec to capture the original function's full parameter signature P = ParamSpec("P") # Define a type variable to capture the original function's return type R = TypeVar("R") # Explicitly link the pre-defined log function to our ParamSpec and TypeVar # This tells static type checkers that log follows the (P) -> R signature log: Callable[P, R] def debug(*args: P.args, **kwargs: P.kwargs) -> R: return log(level="debug", *args, **kwargs)
Why This Works
ParamSpec[P]acts as a dynamic placeholder that captures the full parameter structure of thelogfunction—including positional arguments (*args) and keyword arguments (**kwargs).TypeVar[R]captures the return type oflog, ensuring our wrapper returns exactly the same type as the original function.- By annotating
logasCallable[P, R], we create a direct link between the wrapper's signature andlog's existing type annotations. Static checkers like mypy or pyright will recognize this connection and validate thatdebugadheres strictly tolog's signature.
Key Notes
- This requires Python 3.10 or later (since
ParamSpecwas introduced in 3.10). If you're on an older version, you can use thetyping_extensionsmodule's backportedParamSpec. - The original
logfunction must have complete type annotations for this to work with static type checkers. Iflogwas untyped, static tools wouldn't have enough information to validate the wrapper's signature (as you noted,inspect.signatureis dynamic and won't help with static checks).
Testing the Type Checker
If we try to misuse debug in a way that violates log's signature, the type checker will catch it immediately. For example:
# This throws a type error (log expects `level` as a string, not an integer) debug(level=123, "test message") # This is valid, matching log's expected signature debug("user logged in", user_id=123)
内容的提问来源于stack exchange,提问作者Guillaume

