PEP 637: Integrate feedback from GvR (#1620)
This commit is contained in:
parent
ed5f3b191f
commit
a5b59d6dca
118
pep-0637.rst
118
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
|
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.
|
interest in 2019. Since then there's been renewed interest in the feature.
|
||||||
|
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ a general consensus on an implementation strategy has now been reached.
|
||||||
Use cases
|
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:
|
specification would improve notation and provide additional value:
|
||||||
|
|
||||||
1. To provide a more communicative meaning to the index, preventing e.g. accidental
|
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
|
regarding passed keyword arguments, not how these arguments should be
|
||||||
interpreted and used by the implementing class.
|
interpreted and used by the implementing class.
|
||||||
|
|
||||||
Syntax and Semantics
|
Current status of indexing operation
|
||||||
====================
|
------------------------------------
|
||||||
|
|
||||||
Current status
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
Subscripting ``obj[x]`` is, effectively, an alternate and specialised form of
|
Subscripting ``obj[x]`` is, effectively, an alternate and specialised form of
|
||||||
function call syntax with a number of differences and restrictions compared to
|
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.
|
should behave transparently and symmetrically.
|
||||||
|
|
||||||
The third difference is that functions have names assigned to their
|
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
|
they end up as entries in the args tuple. In other words, functions already
|
||||||
have anonymous argument semantic, exactly like the indexing operation. However,
|
have anonymous argument semantic, exactly like the indexing operation. However,
|
||||||
__(get|set|del)item__ is not always receiving a tuple as the ``index`` argument
|
``__(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::
|
(to be uniform in behavior with ``*args``). In fact, given a trivial class::
|
||||||
|
|
||||||
class X:
|
class X:
|
||||||
def __getitem__(self, index):
|
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"
|
The index operation basically forwards the content of the square brackets "as is"
|
||||||
in the ``index`` argument::
|
in the ``index`` argument::
|
||||||
|
|
||||||
>>> x=X()
|
>>> x=X()
|
||||||
>>> x[0]
|
>>> x[0]
|
||||||
0
|
0
|
||||||
|
@ -224,18 +220,17 @@ this one isn't::
|
||||||
|
|
||||||
a[]
|
a[]
|
||||||
|
|
||||||
|
Specification
|
||||||
|
=============
|
||||||
|
|
||||||
New Proposal
|
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__``
|
||||||
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__``
|
|
||||||
is fundamentally an indexing operation, and the way the element is retrieved,
|
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
|
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, 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
|
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
|
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
|
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
|
``__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.
|
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
|
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
|
create the final index via an enhanced syntax that combines the positional index
|
||||||
and keyword arguments, if passed.
|
and keyword arguments, if passed.
|
||||||
|
|
||||||
The above brings an important point across. Keyword arguments, in the context of the index
|
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
|
will have to accept values that are unconventional for functions. See for
|
||||||
example use case 1, where a slice is accepted.
|
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
|
1. An empty subscript is still illegal, regardless of context::
|
||||||
>>> 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::
|
|
||||||
|
|
||||||
obj[] # SyntaxError
|
obj[] # SyntaxError
|
||||||
|
|
||||||
|
@ -296,7 +267,7 @@ The following old semantics are preserved:
|
||||||
|
|
||||||
del obj[index]
|
del obj[index]
|
||||||
# calls type(obj).__delitem__(obj, index)
|
# calls type(obj).__delitem__(obj, index)
|
||||||
|
|
||||||
This remains the case even if the index is followed by keywords; see point 5 below.
|
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
|
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,
|
called. The same changes should be applied to this method as well,
|
||||||
so that a writing like ``list[T=int]`` can be accepted.
|
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
|
None of what is proposed in this PEP will change the behavior of the current
|
||||||
do not modify their behavior. In other words, if ``d`` is a ``dict``, the
|
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
|
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
|
not support the use of keyword arguments. The same holds for all other classes
|
||||||
(list, frozendict, etc.)
|
(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
|
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::
|
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
|
C Interface
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Resolution of the indexing operation is performed through a call to
|
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, and
|
- ``PyObject_GetItem(PyObject *o, PyObject *key)`` for the get operation
|
||||||
``PyObject_DelItem(PyObject *o, PyObject *key)`` for the del 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
|
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
|
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
|
the signature of this function cannot be changed, and different C level functions
|
||||||
need to be implemented to support the extended call. We propose
|
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.
|
- ``PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)``
|
||||||
Currently, the implementation uses ``BINARY_SUBSCR``, ``STORE_SUBSCR`` and ``DELETE_SUBSCR``
|
- ``PyObject_SetItemWithKeywords(PyObject *o, PyObject *key, PyObject *value, PyObject *kwargs)``
|
||||||
to invoke the old functions. We propose ``BINARY_SUBSCR_EX``,
|
- ``PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)``
|
||||||
``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
|
Additionally, new opcodes will be needed for the enhanced call. Currently, the
|
||||||
will call the extended methods passing ``NULL`` as kwargs.
|
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
|
Workarounds
|
||||||
===========
|
===========
|
||||||
|
|
Loading…
Reference in New Issue