That's a fair criticism -- Protocols are usable for this, but the syntax is hardly the most intuitive, and most people would never think of this approach.
I guess the alternative would be to duplicate the full syntax allowed in the parameter list of a 'def' statement (with some tweaks perhaps), so the following would all be valid types:
(x: float, method: int|None = None) -> str (*args: int, **kwds: int) -> int (a: int, b: int, /) -> int
As for tweaks, maybe we can change things so that (int, int) -> int is equivalent to the latter (since this is presumably still a very common case). The rule would be something like "if there's no /, a / is assumed at the end", and also "in positional args, the syntax is [name ':'] type_expr ['=' default_expr]."
On Sun, Nov 29, 2020 at 12:02 AM Andrew Svetlov email@example.com wrote:
On Sun, Nov 29, 2020 at 1:49 AM Guido van Rossum firstname.lastname@example.org wrote:
On Sat, Nov 28, 2020 at 9:32 AM Andrew Svetlov email@example.com wrote:
I would see support of all argument kinds support in any proposal for a new callable: positional only args, named args, keyword-only, *args and **kwargs. The exact notation in probably less important than missing functionality.
Hm, but using Protocol you can already express every callable type. We could duplicate all of the complexity of 'def' parameter lists into the type notation, but it would be very complex. So this requirement is at least debatable (I'm not actually sure how I feel about it).
Don't get me wrong please. I'm not the typing system developer (while use typing every day in my code). You can consider this email as end-user feedback.
I have a lot of code that uses `Callable` annotation; it is a natural way of annotating any callable, isn't it. Sometimes `Callable` doesn't work. For example, I cannot annotate a function that accepts a keyword-only argument.
Sure, I can use Protocol but it doesn't feel natural and requires more code lines, especially if the Protocol is used once or twice. Also, Protocol adds `self` to methods which looks at least confusing when I annotate plain callables which naturally don't belong to any class:
class MyFunc(typing.Protocol): def __call__(self, /, arg: int) -> str: ...
Moreover, I recall a pull request to only of my libraries that had something similar to:
class MyProto(typing.Protocol): def __call__(self_, /, self: Class, arg: int) -> str: ...
The protocol actually had two 'self': one fake self for protocol itself and another one if the *real* self. The PR was refactored to use a more *natural* style but my first reaction was eyeballs :)
The last thing that is unclear for me is mixing protocols with ParamSpec from PEP-612.
Sure, I can live with all the mentioned limitations and learn all the required tricks if we have a consensus that Callable should operate with positional unnamed arguments only; but allowing to write such things in a manner similar to that Python supports for function definitions already makes life easier.
-- Thanks, Andrew Svetlov