-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@functools.wraps return type doesn't include __wrapped__ attribute #4826
Comments
Unfortunately, I don't see a solution with the current type system. There is no way the "enhance" an existing generic type. I see two options here:
I prefer solution 1, since I believe this uncovers far more problems than not having |
Hm, I might be misunderstanding your point about 2, but it seems like there's already very little checking on the call: import functools
def foo():
pass
@functools.wraps(foo)
def bar(x: int) -> None:
pass
reveal_type(bar) says the type is As such, something like the following (along the lines of your 2), might be an improvement over the current state? from typing import Callable, Any
_AnyCallable = Callable[..., Any]
class Ret:
__wrapped__: _AnyCallable
__call__: _AnyCallable
def wraps(wrapped_f: _AnyCallable) -> Callable[[_AnyCallable], Ret]:
def inner(new_f):
def wrapper(*args, **kwargs):
return new_f(*args, **kwargs)
wrapper.__wrapped__ = wrapped_f
return wrapper
return inner
def foo(x: str) -> bool:
return x.startswith("f")
@wraps(foo)
def bar(x: int) -> str:
return str(x + 1)
reveal_type(bar) # Ret
reveal_type(bar.__call__) # def (*Any, **Any) -> Any
reveal_type(bar.__wrapped__) # def (*Any, **Any) -> Any (The biggest downside I can see here is the type of the wrapped function becomes Going more complicated, one could do something like the following, which preserve types but I think it requires variadic generics python/typing#193 to do properly; for instance, supporting 0 or 2 or 3 or ... arguments. (Collapsed to keep this comment more focused.) from typing import TypeVar, Callable, Generic, Any
WrappedIn = TypeVar("WrappedIn")
WrappedOut = TypeVar("WrappedOut")
NewIn = TypeVar("NewIn")
NewOut = TypeVar("NewOut")
class Ret(Generic[WrappedIn, WrappedOut, NewIn, NewOut]):
__wrapped__: Callable[[Any, WrappedIn], WrappedOut]
__call__: Callable[[Any, NewIn], NewOut]
def wraps(wrapped_f: Callable[[WrappedIn], WrappedOut]) -> Callable[
[Callable[[NewIn], NewOut]],
Ret[WrappedIn, WrappedOut, NewIn, NewOut]
]:
def inner(new_f):
def wrapper(*args, **kwargs):
return new_f(*args, **kwargs)
wrapper.__wrapped__ = wrapped_f
return wrapper
return inner
def foo(x: str) -> bool:
return x.startswith("f")
@wraps(foo)
def baz(x: int) -> str:
return str(x + 1)
baz(1) # ok
baz.__wrapped__("foo") # ok
baz("foo") # error: Argument 1 has incompatible type "str"; expected "int"
baz.__wrapped__(1) # error: Argument 1 has incompatible type "int"; expected "str" All that said, def my_decorator(f: Callable[[int, float], str]) -> Callable[[int], None]:
@wraps(f)
def wrapper(x: int) -> None:
print(f(x, x + 0.5))
return wrapper
@my_decorator
def foo(x: int, y: float) -> str:
return f"{x} {y}" As such, this may be little value to refine. |
You're not using typeshed master, which has #4743 |
@huonw I noticed you referenced python/typing#193 on variadic generics in this thread. Heads up that we've been working on a draft of a PEP for this in PEP 646. If this is something you still care about, take a read and let us know any feedback in this thread in typing-sig. Thanks! |
I am going to close this for now, as is standard for issues labeled "inexpressible". We can revisit this when PEP 646 is implemented in supported type checkers. |
Since Python 3.2, the
functools.wraps(f)
decorator adds a__wrapped__
attribute, set to the original functionf
. The following (unconventional) use ofwraps
runs successfully and demonstrates this:However, this doesn't pass type checking, likely because the
wraps
function is annotated as returning a plainCallable
which doesn't include anything about the__wrapped__
attribute:typeshed/stdlib/3/functools.pyi
Line 58 in 3ae99d1
Errors from mypy https://mypy-play.net/?mypy=0.790&python=3.9&gist=8b06bab355f47dbabf1f8eb42c047a62 :
The text was updated successfully, but these errors were encountered: