PEP 637: Integrate feedback from GvR (#1620)
This commit is contained in:
parent
ed5f3b191f
commit
a5b59d6dca
114
pep-0637.rst
114
pep-0637.rst
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
===========
|
||||
|
|
Loading…
Reference in New Issue