-
-
Notifications
You must be signed in to change notification settings - Fork 2.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
Make any callable compatible with (*args: Any, **kwargs: Any)? #5876
Comments
I actually like this idea, I have seen this confusion several times, and although it is a bit unsafe, most of the time when people write |
Agreed, this is a case where practicality beats purity.
|
I just stumbled upon this trying to build a Protocol to require a callable that takes an Event as the first parameter:
Just throwing this out here, but maybe a variant of Any could be useful for defining callback signatures:
Or maybe the call could be decorated with something like Another similar (but different) use case would be to require the method to include a specific kwarg (or **kwargs), regardless of its position. I feel these will be hard to implement without the help of some decorators? |
I do read the following words in mypy docs
Does it mean this issue has been resolved? Correct me if I am wrong. |
@ckyogg: no, the issue here is to be able to enforce partial signatures to improve duck typing possibilities. Your excerpt is asking for "any callback whatsoever" but i want to define "a callback with a quit flag as first argument and anything else after" |
Has anyone found a good solution for this? My usecase is exactly the same as @jonapich I want to enforce the first argument and allow anything afterwards. |
+1. It seems like the only solution right now is to create a union type alias covering all cases, which is obviously something I'd like to avoid doing! class CallbackWithKwargs1(Protocol[T_contra, T_co]):
def __call__(self, __element: T_contra, __arg1=...) -> T_co:
pass
class CallbackWithKwargs2(Protocol[T_contra, T_co]):
def __call__(self, __element: T_contra, __arg1=..., __arg2=...) -> T_co:
pass
...
CallbackWithKwargs=Union[
CallbackWithKwargs1, CallbackWithKwargs2, ...] |
Another common real-world example is trying to create a type for a functional view in Django, where the first argument is always a |
@antonagestam perhaps #8263 could help resolve this issue? |
I really just care about typing a function (in a prototype) with arbitrary arguments (except for perhaps |
@jmehnle does something like |
Tip for achieving this at present: from typing import Callable, Any
class Parent:
method: Callable[..., int]
def method( # type: ignore
self, *args: Any, **kwargs: Any,
) -> int:
...
class Child(Parent):
def method(self, x: str) -> int:
... Try it: https://mypy-play.net/?mypy=latest&python=3.8&gist=ba254920ee62608c5696c29610b2a379 |
Doesn't the recently-added |
It's still an issue for subclasses, which is the main thing in the original post. This is one of mypy's most reacted-to open issues, so definitely some real usability issues here. |
Are there good examples of when a base class would want to expose a member that accepts arbitrary parameters, but has a derived class that doesn't?
Some of that reaction may have preceded the addition of |
Check out some of the issues that link to here. Yes, this is obviously unsafe if the parent actually accepts everything, which is why this hasn't been done yet. The change itself is trivial, see #11203 |
@hauntsaninja Yeah, I looked at a few. I think MyPy is correct. These are LSP violations and indicative of poor code. It would be better in my opinion to just expand the documentation to explain workarounds. For example, in #9174, the methods |
Resolves #5876 Co-authored-by: hauntsaninja <> Co-authored-by: Ivan Levkivskyi <[email protected]>
Since this has been merged and I got a question about it already, if your parent class actually accepts everything, use |
How did you end up resolving this issue? The PR appears to have only solved the case when the entirety of your function signature is Here is an example of code I am attempting to annotate, with no success. I was able to use from functools import wraps
from typing import TypeVar, Any, Protocol, Callable
class A():
pass
class B(A):
pass
A_co = TypeVar("A_co", bound=A, covariant=True)
class PreFunc(Protocol[A_co]):
def __call__(self, length_table: int, *args: Any, **kwargs: Any) -> A_co:
...
PostFunc = Callable[..., int]
def add_table_to_args(table_name: str) -> Callable[[PreFunc[A_co]], PostFunc]:
def decorator(func: PreFunc[A_co]) -> PostFunc:
@wraps(func)
def wrapped_func(*args: Any, **kwargs: Any) -> int:
test = func(len(table_name), *args, **kwargs)
return 4
return wrapped_func
return decorator
@add_table_to_args("random string") # Argument 1 has incompatible type "Callable[[int], B]"; expected "PreFunc[<nothing>]"
def test_function(length_table: int) -> B:
return B() |
For your problem, is it possible to use The pull request that closed this issue was really about providing some notation for In more general cases where you want to have a generic method that accepts some required parameters, it may be better to choose a design that doesn't have LSP errors, like: class PreFuncExtra: ...
class PreFunc(Protocol[A_co]):
def __call__(self, length_table: int, pre_func_extra: PreFuncExtra) -> A_co:
... And then you can simply derive from PreFuncExtra in your derived classes to add data members. Your derive classes should all accept Even if you were able to use the arbitrary args/kwargs solution that you want, you would have to assert on types, and you would end up asserting on many more types rather than a single instance of PreFuncExtra. |
It certainly is possible - as I noted above, using Making a separate sub-class annotation for every variant of arguments is not really a practical solution for me. This is a decorator that needs to be usable by anyone, anywhere, as long as their function accepts an |
In the case of annotating a decorator, you should use Using |
Following python/mypy#5876 recommendation around how to make generic callback protocols, this seems like the best way overall
Perhaps a callable that accepts arbitrary arguments by having
(*args: Any, **kwargs: Any)
could be treated similarly toCallable[..., X]
(with explicit...
), i.e. a callable with arbitrary arguments would be compatible with it.The reason for this is that there's no easy way to annotate a function so that the type would be
Callable[..., X]
, even though this seems to be a somewhat common need. Users sometimes assume that using(*args: Any, **kwargs: Any)
will work for this purpose and are confused when it doesn't. python/typing#264 (comment) is a recent example.Example:
We could perhaps extend this to handle decorators as well:
The text was updated successfully, but these errors were encountered: