PEP 637: Integrate feedback from GvR (#1620)

This commit is contained in:
Stefano Borini 2020-09-26 21:32:35 +01:00 committed by GitHub
parent ed5f3b191f
commit a5b59d6dca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 48 additions and 70 deletions

View File

@ -36,7 +36,6 @@ and would also provide appropriate semantics.
This PEP is a successor to PEP 472, which was rejected due to lack of
interest in 2019. Since then there's been renewed interest in the feature.
Overview
========
@ -70,7 +69,7 @@ a general consensus on an implementation strategy has now been reached.
Use cases
---------
The following practical use cases present different cases where a keyworded
The following practical use cases present different cases where a keyword
specification would improve notation and provide additional value:
1. To provide a more communicative meaning to the index, preventing e.g. accidental
@ -124,15 +123,12 @@ implementation. This PEP only defines and dictates the behavior of python
regarding passed keyword arguments, not how these arguments should be
interpreted and used by the implementing class.
Syntax and Semantics
====================
Current status of indexing operation
------------------------------------
Current status
--------------
Before attacking the problem of detailing the new syntax and semantics to the
indexing notation, it is relevant to analyse how the indexing notation works
today, in which contexts, and how it is different from a function call.
Before detailing the new syntax and semantics to the indexing notation, it is
relevant to analyse how the indexing notation works today, in which contexts,
and how it is different from a function call.
Subscripting ``obj[x]`` is, effectively, an alternate and specialised form of
function call syntax with a number of differences and restrictions compared to
@ -177,11 +173,11 @@ imbalance between the two forms. It is therefore not a given that the two
should behave transparently and symmetrically.
The third difference is that functions have names assigned to their
arguments, unless the passed parameters are captured with \*args, in which case
arguments, unless the passed parameters are captured with ``*args``, in which case
they end up as entries in the args tuple. In other words, functions already
have anonymous argument semantic, exactly like the indexing operation. However,
__(get|set|del)item__ is not always receiving a tuple as the ``index`` argument
(to be uniform in behavior with \*args). In fact, given a trivial class::
``__(get|set|del)item__`` is not always receiving a tuple as the ``index`` argument
(to be uniform in behavior with ``*args``). In fact, given a trivial class::
class X:
def __getitem__(self, index):
@ -189,7 +185,7 @@ __(get|set|del)item__ is not always receiving a tuple as the ``index`` argument
The index operation basically forwards the content of the square brackets "as is"
in the ``index`` argument::
>>> x=X()
>>> x[0]
0
@ -224,18 +220,17 @@ this one isn't::
a[]
Specification
=============
New Proposal
------------
Before describing the new proposal, it is important to stress the difference in
nomenclature between _index_ and keyword _argument_, as it is important to
understand the fundamental asymmetry between the two. The ``__(get|set|del)item__``
Before describing the specification, it is important to stress the difference in
nomenclature between *positional index*, *final index* and *keyword argument*, as it is important to
understand the fundamental asymmetries at play. The ``__(get|set|del)item__``
is fundamentally an indexing operation, and the way the element is retrieved,
set, or deleted is through an index.
set, or deleted is through an index, the *final index*.
The current status quo is to build a _final_ index from what is passed between
square brackets, the _positional_ index. In other words, what is passed in the
The current status quo is to directly build the *final index* from what is passed between
square brackets, the *positional index*. In other words, what is passed in the
square brackets is trivially used to generate what the code in ``__getitem__`` then uses
for the indicisation operation. As we already saw for the dict, ``d[1]`` has a
positional index of ``1`` and also a final index of ``1`` (because it's the element that is
@ -247,8 +242,8 @@ unhashable. The positional index is what is currently known as the ``index`` par
``__getitem__``. Nevertheless, nothing prevents to construct a dictionary-like class that
creates the final index by e.g. converting the positional index to a string.
The new proposal extends the current status quo, and grants more flexibility to
create the _final_ index via an enhanced syntax that combines the positional index
This PEP extends the current status quo, and grants more flexibility to
create the final index via an enhanced syntax that combines the positional index
and keyword arguments, if passed.
The above brings an important point across. Keyword arguments, in the context of the index
@ -256,33 +251,9 @@ operation, may be used to take indexing decisions to obtain the final index, and
will have to accept values that are unconventional for functions. See for
example use case 1, where a slice is accepted.
The new notation will make all of the following valid notation::
The successful implementation of this PEP will result in the following behavior:
>>> a[1] # Current case, single index
>>> a[1, 2] # Current case, multiple indexes
>>> a[1, 2:5] # Current case, slicing.
>>> a[3, R=3, K=4] # New case. Single index, and keyword arguments
>>> a[K=3, R=2] # New case. No index with keyword arguments
>>> a[3, R=3:10, K=4] # New case. Slice in keyword argument
>>> a[3, R=..., K=4] # New case. Ellipsis in keyword argument
The new notation will NOT make the following valid notation::
>>> a[] # INVALID. No index and no keyword arguments.
It is worth stressing out that none of what is proposed in this PEP will change
the behavior of the current core classes that use indexing. Adding keywords to
the index operation for custom classes is not the same as modifying e.g. the
standard dict type to handle keyword arguments. In fact, dict (as well as list and other
stdlib classes with indexing semantics) will remain the same and will continue
not to accept keyword arguments.
Syntax and Semantics
====================
The following old semantics are preserved:
1. As said above, an empty subscript is still illegal, regardless of context::
1. An empty subscript is still illegal, regardless of context::
obj[] # SyntaxError
@ -296,7 +267,7 @@ The following old semantics are preserved:
del obj[index]
# calls type(obj).__delitem__(obj, index)
This remains the case even if the index is followed by keywords; see point 5 below.
3. Comma-seperated arguments are still parsed as a tuple and passed as
@ -428,11 +399,15 @@ The following old semantics are preserved:
called. The same changes should be applied to this method as well,
so that a writing like ``list[T=int]`` can be accepted.
Existing indexing implementations in standard classes
-----------------------------------------------------
Indexing behavior in standard classes (dict, list, etc.)
--------------------------------------------------------
As said before, we recommend that current classes that use indexing operations
do not modify their behavior. In other words, if ``d`` is a ``dict``, the
None of what is proposed in this PEP will change the behavior of the current
core classes that use indexing. Adding keywords to the index operation for
custom classes is not the same as modifying e.g. the standard dict type to
handle keyword arguments. In fact, dict (as well as list and other stdlib
classes with indexing semantics) will remain the same and will continue not to
accept keyword arguments. In other words, if ``d`` is a ``dict``, the
statement ``d[1, a=2]`` will raise ``TypeError``, as their implementation will
not support the use of keyword arguments. The same holds for all other classes
(list, frozendict, etc.)
@ -440,7 +415,7 @@ not support the use of keyword arguments. The same holds for all other classes
Corner case and Gotchas
-----------------------
With the introduction of the new notation, a few corner cases need to be analysed:
With the introduction of the new notation, a few corner cases need to be analysed.
1. Technically, if a class defines their getter like this::
@ -541,25 +516,28 @@ With the introduction of the new notation, a few corner cases need to be analyse
C Interface
===========
Resolution of the indexing operation is performed through a call to
``PyObject_GetItem(PyObject *o, PyObject *key)`` for the get operation,
``PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)`` for the set operation, and
``PyObject_DelItem(PyObject *o, PyObject *key)`` for the del operation.
Resolution of the indexing operation is performed through a call to the following functions
- ``PyObject_GetItem(PyObject *o, PyObject *key)`` for the get operation
- ``PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)`` for the set operation
- ``PyObject_DelItem(PyObject *o, PyObject *key)`` for the del operation
These functions are used extensively within the python executable, and are
also part of the public C API, as exported by ``Include/abstract.h``. It is clear that
the signature of this function cannot be changed, and different C level functions
need to be implemented to support the extended call. We propose
``PyObject_GetItemEx(PyObject *o, PyObject *key, PyObject *kwargs)``,
``PyObject_SetItemEx(PyObject *o, PyObject *key, PyObject *value, PyObject *kwargs)`` and
``PyObject_DetItemEx(PyObject *o, PyObject *key, PyObject *kwargs)``.
Additionally, new opcodes will be needed for the enhanced call.
Currently, the implementation uses ``BINARY_SUBSCR``, ``STORE_SUBSCR`` and ``DELETE_SUBSCR``
to invoke the old functions. We propose ``BINARY_SUBSCR_EX``,
``STORE_SUBSCR_EX`` and ``DELETE_SUBSCR_EX`` for the extended operation. The parser will
have to generate these new opcodes. The ``PyObject_(Get|Set|Del)Item`` implementations
will call the extended methods passing ``NULL`` as kwargs.
- ``PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)``
- ``PyObject_SetItemWithKeywords(PyObject *o, PyObject *key, PyObject *value, PyObject *kwargs)``
- ``PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)``
Additionally, new opcodes will be needed for the enhanced call. Currently, the
implementation uses ``BINARY_SUBSCR``, ``STORE_SUBSCR`` and ``DELETE_SUBSCR``
to invoke the old functions. We propose ``BINARY_SUBSCR_KW``,
``STORE_SUBSCR_KW`` and ``DELETE_SUBSCR_KW`` for the new operations. The
compiler will have to generate these new opcodes. The
old C implementations will call the extended methods passing ``NULL``
as kwargs.
Workarounds
===========