PEP 677: Include more rejected alternatives (#2219)
* Add more rejected alternatives - Rust-style syntax - Forced outer parentheses - Improving readability of existing callable type * Add discussion of alternative | precedence * Add blank lines for bullet lists * Fixes based on initial review * More tweaks from review * More updates based on review
This commit is contained in:
parent
79c2ad8b9f
commit
5cb9636c4c
138
pep-0677.rst
138
pep-0677.rst
|
@ -826,6 +826,20 @@ replacement for callable types:
|
|||
more like a shorter way to write them than a replacement for
|
||||
``Callable``.
|
||||
|
||||
Hybrid keyword-arrow Syntax
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the Rust language, a keyword ``fn`` is used to indicate functions
|
||||
in much the same way as Python's ``def``, and callable types are
|
||||
indicated using a hybrid arrow syntax ``Fn(i64, String) -> bool``.
|
||||
|
||||
We could use the ``def`` keyword in callable types for Python, for
|
||||
example our two-parameter boolean function could be written as
|
||||
``def(int, str) -> bool``. But we think this might confuse readers
|
||||
into thinking ``def(A, B) -> C`` is a lambda, particularly because
|
||||
Javascript's ``function`` keyword is used in both named and anonymous
|
||||
functions.
|
||||
|
||||
Parenthesis-Free Syntax
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -839,6 +853,99 @@ existing function header syntax. Moreover, it is visually similar to
|
|||
lambdas, which bind names with no parentheses: ``lambda x, y: x ==
|
||||
y``.
|
||||
|
||||
Requiring Outer Parentheses
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A concern with the current proposal is readability, particularly
|
||||
when callable types are used in return type position which leads to
|
||||
multiple top-level ``->`` tokens, for example::
|
||||
|
||||
def make_adder() -> (int) -> int:
|
||||
return lambda x: x + 1
|
||||
|
||||
We considered a few ideas to prevent this by changing rules about
|
||||
parentheses. One was to move the parentheses to the outside, so
|
||||
that a two-argument boolean function is written ``(int, str -> bool)``.
|
||||
With this change, the example above becomes::
|
||||
|
||||
def make_adder() -> (int -> int):
|
||||
return lambda x: x + 1
|
||||
|
||||
This makes the nesting of many examples that are difficult to
|
||||
follow clear, but we rejected it because
|
||||
|
||||
- Currently in Python commas bind very loosely, which means it might be common
|
||||
to misread ``(int, str -> bool)`` as a tuple whose first element is an int,
|
||||
rather than a two-parameter callable type.
|
||||
- It is not very similar to function header syntax, and one of our goals was
|
||||
familiar syntax inspired by function headers.
|
||||
- This syntax may be more readable for deaply nested callables like the one
|
||||
above, but deep nesting is not very common. Encouraging extra parentheses
|
||||
around callable types in return position via a style guide would have most of
|
||||
the readability benefit without the downsides.
|
||||
|
||||
We also considered requiring parentheses on both the parameter list and the
|
||||
outside, e.g. ``((int, str) -> bool)``. With this change, the example above
|
||||
becomes::
|
||||
|
||||
def make_adder() -> ((int) -> int):
|
||||
return lambda x: x + 1
|
||||
|
||||
We rejected this change because:
|
||||
|
||||
- The outer parentheses only help readability in some cases, mostly when a
|
||||
callable type is used in return position. In many other cases they hurt
|
||||
readability rather than helping.
|
||||
- We agree that it might make sense to encourage outer parentheses in several
|
||||
cases, particularly callable types in function return annotations. But
|
||||
|
||||
- We believe it is more appropriate to encourage this in style guides,
|
||||
linters, and autoformatters than to bake it into the parser and throw
|
||||
syntax errors.
|
||||
- Moreover, if a type is complicated enough that readability is a concern
|
||||
we can always use type aliases, for example::
|
||||
|
||||
IntToIntFunction: (int) -> int
|
||||
|
||||
def make_adder() -> IntToIntFunction:
|
||||
return lambda x: x + 1
|
||||
|
||||
|
||||
Making ``->`` bind tighter than ``|``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to allow both ``->`` and ``|`` tokens in type expressions we
|
||||
had to choose precedence. In the current proposal, this is a function
|
||||
returning an optional boolean::
|
||||
|
||||
(int, str) -> bool | None # equivalent ot (int, str) -> (bool | None)
|
||||
|
||||
We considered having ``->`` bind tighter so that instead the expression
|
||||
would parse as ``((int, str) -> bool) | None``. There are two advantages
|
||||
to this:
|
||||
|
||||
- It means we no would longer have to treat ``None | (int, str) ->
|
||||
bool`` as a syntax error.
|
||||
- Looking at typeshed today, optional callable arguments are very common
|
||||
because using ``None`` as a default value is a standard Python idiom.
|
||||
Having ``->`` bind tighter would make these easier to write.
|
||||
|
||||
We decided against this for a few reasons:
|
||||
|
||||
- The function header ``def f() -> int | None: ...`` is legal
|
||||
and indicates a function returning an optional int. To be consistent
|
||||
with function headers, callable types should do the same.
|
||||
- TypeScript is the other popular language we know of that uses both
|
||||
``->`` and ``|`` tokens in type expressions, and they have ``|`` bind
|
||||
tighter. While we do not have to follow their lead, we prefer to do
|
||||
so.
|
||||
- We do acknowledge that optional callable types are common and
|
||||
having ``|`` bind tighter forces extra parentheses, which makes these
|
||||
types harder to write. But code is read more often than written, and
|
||||
we believe that requiring the outer parentheses for an optional callable
|
||||
type like ``((int, str) -> bool) | None`` is preferable for readability.
|
||||
|
||||
|
||||
Introducing type-strings
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -850,6 +957,37 @@ from the Steering Council on ensuring that type expressions do not
|
|||
diverge from the rest of Python's syntax.
|
||||
|
||||
|
||||
Improving Usability of the Indexed Callable Type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If we do not want to add new syntax for callable types, we could
|
||||
look at how to make the existing type easier to read. One proposal
|
||||
would be to make the builtin ``callable`` function indexable so
|
||||
that it could be used as a type::
|
||||
|
||||
callable[[int, str], bool]
|
||||
|
||||
This change would be analogous to PEP 585 that made built in collections
|
||||
like ``list`` and ``dict`` usable as types, and would make imports
|
||||
more convenient, but it wouldn't help readability of the types themselves
|
||||
much.
|
||||
|
||||
In order to reduce the number of brackets needed in complex callable
|
||||
types, it would be possible to allow tuples for the argument list::
|
||||
|
||||
callable[(int, str), bool]
|
||||
|
||||
This actually is a significant readability improvement for
|
||||
multi-argument functions, but the problem is that it makes callables
|
||||
with one arguments, which are the most common arity, hard to
|
||||
write: because ``(x)`` evaluates to ``x``, they would have to be
|
||||
written like ``callable[(int,), bool]``. This is awkward enough that
|
||||
we dislike this idea.
|
||||
|
||||
Moreover, none of these ideas help as much with reducing verbosity
|
||||
as the current proposal, nor do they introduce as strong a visual cue
|
||||
as the ``->`` between the parameter types and the return type.
|
||||
|
||||
Backward Compatibility
|
||||
======================
|
||||
|
||||
|
|
Loading…
Reference in New Issue