PEP 637: Renamed file to rst and changed formatting of block code (#1619)
This commit is contained in:
parent
ef4663634e
commit
8e16d32598
|
@ -20,21 +20,16 @@ At present keyword arguments are allowed in function calls, but not in
|
||||||
item access. This PEP proposes that Python be extended to allow keyword
|
item access. This PEP proposes that Python be extended to allow keyword
|
||||||
arguments in item access.
|
arguments in item access.
|
||||||
|
|
||||||
The following example shows keyword arguments for ordinary function calls:
|
The following example shows keyword arguments for ordinary function calls::
|
||||||
|
|
||||||
::
|
>>> val = f(1, 2, a=3, b=4)
|
||||||
|
|
||||||
>>> val = f(1, 2, a=3, b=4)
|
|
||||||
|
|
||||||
The proposal would extend the syntax to allow a similar construct
|
The proposal would extend the syntax to allow a similar construct
|
||||||
to indexing operations:
|
to indexing operations::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
>>> val = x[1, 2, a=3, b=4] # getitem
|
|
||||||
>>> x[1, 2, a=3, b=4] = val # setitem
|
|
||||||
>>> del x[1, 2, a=3, b=4] # delitem
|
|
||||||
|
|
||||||
|
>>> val = x[1, 2, a=3, b=4] # getitem
|
||||||
|
>>> x[1, 2, a=3, b=4] = val # setitem
|
||||||
|
>>> del x[1, 2, a=3, b=4] # delitem
|
||||||
|
|
||||||
and would also provide appropriate semantics.
|
and would also provide appropriate semantics.
|
||||||
|
|
||||||
|
@ -79,66 +74,50 @@ The following practical use cases present different cases where a keyworded
|
||||||
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
|
||||||
inversion of indexes
|
inversion of indexes::
|
||||||
|
|
||||||
::
|
>>> grid_position[x=3, y=5, z=8]
|
||||||
|
>>> rain_amount[time=0:12, location=location]
|
||||||
|
>>> matrix[row=20, col=40]
|
||||||
|
|
||||||
>>> grid_position[x=3, y=5, z=8]
|
2. To enrich the typing notation with keywords, especially during the use of generics::
|
||||||
>>> rain_amount[time=0:12, location=location]
|
|
||||||
>>> matrix[row=20, col=40]
|
|
||||||
|
|
||||||
|
|
||||||
2. To enrich the typing notation with keywords, especially during the use of generics
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
def function(value: MyType[T=int]):
|
|
||||||
|
|
||||||
|
def function(value: MyType[T=int]):
|
||||||
|
|
||||||
3. In some domain, such as computational physics and chemistry, the use of a
|
3. In some domain, such as computational physics and chemistry, the use of a
|
||||||
notation such as ``Basis[Z=5]`` is a Domain Specific Language notation to represent
|
notation such as ``Basis[Z=5]`` is a Domain Specific Language notation to represent
|
||||||
a level of accuracy
|
a level of accuracy::
|
||||||
|
|
||||||
::
|
>>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
|
||||||
|
|
||||||
>>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
|
4. Pandas currently uses a notation such as::
|
||||||
|
|
||||||
4. Pandas currently uses a notation such as
|
>>> df[df['x'] == 1]
|
||||||
|
|
||||||
::
|
which could be replaced with ``df[x=1]``.
|
||||||
|
|
||||||
>>> df[df['x'] == 1]
|
5. xarray has named dimensions. Currently these are handled with functions .isel::
|
||||||
|
|
||||||
which could be replaced with df[x=1].
|
>>> data.isel(row=10) # Returns the tenth row
|
||||||
|
|
||||||
5. xarray has named dimensions. Currently these are handled with functions .isel:
|
which could also be replaced with ``data[row=10]``. A more complex example::
|
||||||
|
|
||||||
::
|
>>> # old syntax
|
||||||
|
>>> da.isel(space=0, time=slice(None, 2))[...] = spam
|
||||||
|
>>> # new syntax
|
||||||
|
>>> da[space=0, time=:2] = spam
|
||||||
|
|
||||||
>>> data.isel(row=10) # Returns the tenth row
|
Another example::
|
||||||
|
|
||||||
which could also be replaced with `data[row=10]`. A more complex example:
|
>>> # old syntax
|
||||||
|
>>> ds["empty"].loc[dict(lon=5, lat=6)] = 10
|
||||||
::
|
>>> # new syntax
|
||||||
|
>>> ds["empty"][lon=5, lat=6] = 10
|
||||||
>>> # old syntax
|
|
||||||
>>> da.isel(space=0, time=slice(None, 2))[...] = spam
|
>>> # old syntax
|
||||||
>>> # new syntax
|
>>> ds["empty"].loc[dict(lon=slice(1, 5), lat=slice(3, None))] = 10
|
||||||
>>> da[space=0, time=:2] = spam
|
>>> # new syntax
|
||||||
|
>>> ds["empty"][lon=1:5, lat=6:] = 10
|
||||||
Another example:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
>>> # old syntax
|
|
||||||
>>> ds["empty"].loc[dict(lon=5, lat=6)] = 10
|
|
||||||
>>> # new syntax
|
|
||||||
>>> ds["empty"][lon=5, lat=6] = 10
|
|
||||||
|
|
||||||
>>> # old syntax
|
|
||||||
>>> ds["empty"].loc[dict(lon=slice(1, 5), lat=slice(3, None))] = 10
|
|
||||||
>>> # new syntax
|
|
||||||
>>> ds["empty"][lon=1:5, lat=6:] = 10
|
|
||||||
|
|
||||||
It is important to note that how the notation is interpreted is up to the
|
It is important to note that how the notation is interpreted is up to the
|
||||||
implementation. This PEP only defines and dictates the behavior of python
|
implementation. This PEP only defines and dictates the behavior of python
|
||||||
|
@ -159,13 +138,11 @@ 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
|
||||||
``obj(x)``. The current python syntax focuses exclusively on position to express
|
``obj(x)``. The current python syntax focuses exclusively on position to express
|
||||||
the index, and also contains syntactic sugar to refer to non-punctiform
|
the index, and also contains syntactic sugar to refer to non-punctiform
|
||||||
selection (slices). Some common examples:
|
selection (slices). Some common examples::
|
||||||
|
|
||||||
::
|
>>> a[3] # returns the fourth element of 'a'
|
||||||
|
>>> a[1:10:2] # slice notation (extract a non-trivial data subset)
|
||||||
>>> a[3] # returns the fourth element of 'a'
|
>>> a[3, 2] # multiple indexes (for multidimensional arrays)
|
||||||
>>> a[1:10:2] # slice notation (extract a non-trivial data subset)
|
|
||||||
>>> a[3, 2] # multiple indexes (for multidimensional arrays)
|
|
||||||
|
|
||||||
This translates into a ``__(get|set|del)item__`` dunder call which is passed a single
|
This translates into a ``__(get|set|del)item__`` dunder call which is passed a single
|
||||||
parameter containing the index (for ``__getitem__`` and ``__delitem__``) or two parameters
|
parameter containing the index (for ``__getitem__`` and ``__delitem__``) or two parameters
|
||||||
|
@ -185,19 +162,15 @@ violate this intrinsic meaning.
|
||||||
The second difference of the indexing notation compared to a function
|
The second difference of the indexing notation compared to a function
|
||||||
is that indexing can be used for both getting and setting operations.
|
is that indexing can be used for both getting and setting operations.
|
||||||
In python, a function cannot be on the left hand side of an assignment. In
|
In python, a function cannot be on the left hand side of an assignment. In
|
||||||
other words, both of these are valid
|
other words, both of these are valid::
|
||||||
|
|
||||||
::
|
>>> x = a[1, 2]
|
||||||
|
>>> a[1, 2] = 5
|
||||||
|
|
||||||
>>> x = a[1, 2]
|
but only the first one of these is valid::
|
||||||
>>> a[1, 2] = 5
|
|
||||||
|
|
||||||
but only the first one of these is valid
|
>>> x = f(1, 2)
|
||||||
|
>>> f(1, 2) = 5 # invalid
|
||||||
::
|
|
||||||
|
|
||||||
>>> x = f(1, 2)
|
|
||||||
>>> f(1, 2) = 5 # invalid
|
|
||||||
|
|
||||||
This asymmetry is important, and makes one understand that there is a natural
|
This asymmetry is important, and makes one understand that there is a natural
|
||||||
imbalance between the two forms. It is therefore not a given that the two
|
imbalance between the two forms. It is therefore not a given that the two
|
||||||
|
@ -208,61 +181,48 @@ 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:
|
||||||
::
|
def __getitem__(self, index):
|
||||||
|
print(index)
|
||||||
class X:
|
|
||||||
def __getitem__(self, index):
|
|
||||||
print(index)
|
|
||||||
|
|
||||||
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[0]
|
||||||
>>> x=X()
|
0
|
||||||
>>> x[0]
|
>>> x[0, 1]
|
||||||
0
|
(0, 1)
|
||||||
>>> x[0, 1]
|
>>> x[(0, 1)]
|
||||||
(0, 1)
|
(0, 1)
|
||||||
>>> x[(0, 1)]
|
>>>
|
||||||
(0, 1)
|
>>> x[()]
|
||||||
>>>
|
()
|
||||||
>>> x[()]
|
>>> x[{1, 2, 3}]
|
||||||
()
|
{1, 2, 3}
|
||||||
>>> x[{1, 2, 3}]
|
>>> x["hello"]
|
||||||
{1, 2, 3}
|
hello
|
||||||
>>> x["hello"]
|
>>> x["hello", "hi"]
|
||||||
hello
|
('hello', 'hi')
|
||||||
>>> x["hello", "hi"]
|
|
||||||
('hello', 'hi')
|
|
||||||
|
|
||||||
The fourth difference is that the indexing operation knows how to convert
|
The fourth difference is that the indexing operation knows how to convert
|
||||||
colon notations to slices, thanks to support from the parser. This is valid
|
colon notations to slices, thanks to support from the parser. This is valid::
|
||||||
|
|
||||||
::
|
a[1:3]
|
||||||
|
|
||||||
a[1:3]
|
this one isn't::
|
||||||
|
|
||||||
this one isn't
|
f(1:3)
|
||||||
|
|
||||||
::
|
The fifth difference is that there's no zero-argument form. This is valid::
|
||||||
|
|
||||||
f(1:3)
|
f()
|
||||||
|
|
||||||
The fifth difference is that there's no zero-argument form. This is valid
|
this one isn't::
|
||||||
|
|
||||||
::
|
a[]
|
||||||
|
|
||||||
f()
|
|
||||||
|
|
||||||
this one isn't
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
a[]
|
|
||||||
|
|
||||||
|
|
||||||
New Proposal
|
New Proposal
|
||||||
|
@ -296,9 +256,7 @@ 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 new notation will make all of the following valid notation::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
>>> a[1] # Current case, single index
|
>>> a[1] # Current case, single index
|
||||||
>>> a[1, 2] # Current case, multiple indexes
|
>>> a[1, 2] # Current case, multiple indexes
|
||||||
|
@ -308,9 +266,7 @@ The new notation will make all of the following valid notation:
|
||||||
>>> a[3, R=3:10, K=4] # New case. Slice in keyword argument
|
>>> a[3, R=3:10, K=4] # New case. Slice in keyword argument
|
||||||
>>> a[3, R=..., K=4] # New case. Ellipsis in keyword argument
|
>>> a[3, R=..., K=4] # New case. Ellipsis in keyword argument
|
||||||
|
|
||||||
The new notation will NOT make the following valid notation:
|
The new notation will NOT make the following valid notation::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
>>> a[] # INVALID. No index and no keyword arguments.
|
>>> a[] # INVALID. No index and no keyword arguments.
|
||||||
|
|
||||||
|
@ -326,81 +282,70 @@ Syntax and Semantics
|
||||||
|
|
||||||
The following old semantics are preserved:
|
The following old semantics are preserved:
|
||||||
|
|
||||||
1. As said above, an empty subscript is still illegal, regardless of context.
|
1. As said above, an empty subscript is still illegal, regardless of context::
|
||||||
|
|
||||||
::
|
obj[] # SyntaxError
|
||||||
|
|
||||||
obj[] # SyntaxError
|
2. A single index value remains a single index value when passed::
|
||||||
|
|
||||||
2. A single index value remains a single index value when passed:
|
obj[index]
|
||||||
|
# calls type(obj).__getitem__(obj, index)
|
||||||
|
|
||||||
|
obj[index] = value
|
||||||
|
# calls type(obj).__setitem__(obj, index, value)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
obj[index]
|
|
||||||
# calls type(obj).__getitem__(obj, index)
|
|
||||||
|
|
||||||
obj[index] = value
|
|
||||||
# calls type(obj).__setitem__(obj, index, value)
|
|
||||||
|
|
||||||
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
|
3. Comma-seperated arguments are still parsed as a tuple and passed as
|
||||||
a single positional argument:
|
a single positional argument::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
obj[spam, eggs]
|
|
||||||
# calls type(obj).__getitem__(obj, (spam, eggs))
|
|
||||||
|
|
||||||
obj[spam, eggs] = value
|
|
||||||
# calls type(obj).__setitem__(obj, (spam, eggs), value)
|
|
||||||
|
|
||||||
del obj[spam, eggs]
|
|
||||||
# calls type(obj).__delitem__(obj, (spam, eggs))
|
|
||||||
|
|
||||||
|
obj[spam, eggs]
|
||||||
|
# calls type(obj).__getitem__(obj, (spam, eggs))
|
||||||
|
|
||||||
|
obj[spam, eggs] = value
|
||||||
|
# calls type(obj).__setitem__(obj, (spam, eggs), value)
|
||||||
|
|
||||||
|
del obj[spam, eggs]
|
||||||
|
# calls type(obj).__delitem__(obj, (spam, eggs))
|
||||||
|
|
||||||
The points above mean that classes which do not want to support keyword
|
The points above mean that classes which do not want to support keyword
|
||||||
arguments in subscripts need do nothing at all, and the feature is therefore
|
arguments in subscripts need do nothing at all, and the feature is therefore
|
||||||
completely backwards compatible.
|
completely backwards compatible.
|
||||||
|
|
||||||
4. Keyword arguments, if any, must follow positional arguments.
|
4. Keyword arguments, if any, must follow positional arguments::
|
||||||
|
|
||||||
::
|
obj[1, 2, spam=None, 3] # SyntaxError
|
||||||
|
|
||||||
obj[1, 2, spam=None, 3] # SyntaxError
|
|
||||||
|
|
||||||
This is like function calls, where intermixing positional and keyword
|
This is like function calls, where intermixing positional and keyword
|
||||||
arguments give a SyntaxError.
|
arguments give a SyntaxError.
|
||||||
|
|
||||||
5. Keyword subscripts, if any, will be handled like they are in
|
5. Keyword subscripts, if any, will be handled like they are in
|
||||||
function calls. Examples:
|
function calls. Examples::
|
||||||
|
|
||||||
::
|
# Single index with keywords:
|
||||||
|
|
||||||
|
obj[index, spam=1, eggs=2]
|
||||||
|
# calls type(obj).__getitem__(obj, index, spam=1, eggs=2)
|
||||||
|
|
||||||
|
obj[index, spam=1, eggs=2] = value
|
||||||
|
# calls type(obj).__setitem__(obj, index, value, spam=1, eggs=2)
|
||||||
|
|
||||||
# Single index with keywords:
|
del obj[index, spam=1, eggs=2]
|
||||||
|
# calls type(obj).__delitem__(obj, index, spam=1, eggs=2)
|
||||||
|
|
||||||
obj[index, spam=1, eggs=2]
|
# Comma-separated indices with keywords:
|
||||||
# calls type(obj).__getitem__(obj, index, spam=1, eggs=2)
|
|
||||||
|
|
||||||
obj[index, spam=1, eggs=2] = value
|
obj[foo, bar, spam=1, eggs=2]
|
||||||
# calls type(obj).__setitem__(obj, index, value, spam=1, eggs=2)
|
# calls type(obj).__getitem__(obj, (foo, bar), spam=1, eggs=2)
|
||||||
|
|
||||||
del obj[index, spam=1, eggs=2]
|
obj[foo, bar, spam=1, eggs=2] = value
|
||||||
# calls type(obj).__delitem__(obj, index, spam=1, eggs=2)
|
# calls type(obj).__setitem__(obj, (foo, bar), value, spam=1, eggs=2)
|
||||||
|
|
||||||
# Comma-separated indices with keywords:
|
del obj[foo, bar, spam=1, eggs=2]
|
||||||
|
# calls type(obj).__detitem__(obj, (foo, bar), spam=1, eggs=2)
|
||||||
obj[foo, bar, spam=1, eggs=2]
|
|
||||||
# calls type(obj).__getitem__(obj, (foo, bar), spam=1, eggs=2)
|
|
||||||
|
|
||||||
obj[foo, bar, spam=1, eggs=2] = value
|
|
||||||
# calls type(obj).__setitem__(obj, (foo, bar), value, spam=1, eggs=2)
|
|
||||||
|
|
||||||
del obj[foo, bar, spam=1, eggs=2]
|
|
||||||
# calls type(obj).__detitem__(obj, (foo, bar), spam=1, eggs=2)
|
|
||||||
|
|
||||||
Note that:
|
Note that:
|
||||||
|
|
||||||
|
@ -431,11 +376,9 @@ The following old semantics are preserved:
|
||||||
- but if no ``**kwargs`` parameter is defined, it is an error.
|
- but if no ``**kwargs`` parameter is defined, it is an error.
|
||||||
|
|
||||||
|
|
||||||
7. Sequence unpacking remains a syntax error inside subscripts:
|
7. Sequence unpacking remains a syntax error inside subscripts::
|
||||||
|
|
||||||
::
|
obj[*items]
|
||||||
|
|
||||||
obj[*items]
|
|
||||||
|
|
||||||
Reason: unpacking items would result it being immediately repacked into
|
Reason: unpacking items would result it being immediately repacked into
|
||||||
a tuple. Anyone using sequence unpacking in the subscript is probably
|
a tuple. Anyone using sequence unpacking in the subscript is probably
|
||||||
|
@ -445,56 +388,43 @@ The following old semantics are preserved:
|
||||||
This restriction has however been considered arbitrary by some, and it might
|
This restriction has however been considered arbitrary by some, and it might
|
||||||
be lifted at a later stage for symmetry with kwargs unpacking, see next.
|
be lifted at a later stage for symmetry with kwargs unpacking, see next.
|
||||||
|
|
||||||
8. Dict unpacking is permitted:
|
8. Dict unpacking is permitted::
|
||||||
|
|
||||||
::
|
items = {'spam': 1, 'eggs': 2}
|
||||||
|
obj[index, **items]
|
||||||
|
# equivalent to obj[index, spam=1, eggs=2]
|
||||||
|
|
||||||
items = {'spam': 1, 'eggs': 2}
|
9. Keyword-only subscripts are permitted. The positional index will be the empty tuple::
|
||||||
obj[index, **items]
|
|
||||||
# equivalent to obj[index, spam=1, eggs=2]
|
|
||||||
|
|
||||||
|
obj[spam=1, eggs=2]
|
||||||
|
# calls type(obj).__getitem__(obj, (), spam=1, eggs=2)
|
||||||
|
|
||||||
|
obj[spam=1, eggs=2] = 5
|
||||||
|
# calls type(obj).__setitem__(obj, (), 5, spam=1, eggs=2)
|
||||||
|
|
||||||
|
del obj[spam=1, eggs=2]
|
||||||
|
# calls type(obj).__delitem__(obj, (), spam=1, eggs=2)
|
||||||
|
|
||||||
9. Keyword-only subscripts are permitted. The positional index will be the empty tuple:
|
10. Keyword arguments must allow slice syntax::
|
||||||
|
|
||||||
::
|
obj[3:4, spam=1:4, eggs=2]
|
||||||
|
# calls type(obj).__getitem__(obj, slice(3, 4, None), spam=slice(1, 4, None), eggs=2)
|
||||||
obj[spam=1, eggs=2]
|
|
||||||
# calls type(obj).__getitem__(obj, (), spam=1, eggs=2)
|
|
||||||
|
|
||||||
obj[spam=1, eggs=2] = 5
|
|
||||||
# calls type(obj).__setitem__(obj, (), 5, spam=1, eggs=2)
|
|
||||||
|
|
||||||
del obj[spam=1, eggs=2]
|
|
||||||
# calls type(obj).__delitem__(obj, (), spam=1, eggs=2)
|
|
||||||
|
|
||||||
|
|
||||||
10. Keyword arguments must allow slice syntax.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
obj[3:4, spam=1:4, eggs=2]
|
|
||||||
# calls type(obj).__getitem__(obj, slice(3, 4, None), spam=slice(1, 4, None), eggs=2)
|
|
||||||
|
|
||||||
This may open up the possibility to accept the same syntax for general function
|
This may open up the possibility to accept the same syntax for general function
|
||||||
calls, but this is not part of this recommendation.
|
calls, but this is not part of this recommendation.
|
||||||
|
|
||||||
11. Keyword arguments must allow Ellipsis
|
11. Keyword arguments must allow Ellipsis::
|
||||||
|
|
||||||
::
|
obj[..., spam=..., eggs=2]
|
||||||
|
# calls type(obj).__getitem__(obj, Ellipsis, spam=Ellipsis, eggs=2)
|
||||||
|
|
||||||
obj[..., spam=..., eggs=2]
|
12. Keyword arguments allow for default values::
|
||||||
# calls type(obj).__getitem__(obj, Ellipsis, spam=Ellipsis, eggs=2)
|
|
||||||
|
|
||||||
|
# Given type(obj).__getitem__(obj, index, spam=True, eggs=2)
|
||||||
12. Keyword arguments allow for default values
|
obj[3] # Valid. index = 3, spam = True, eggs = 2
|
||||||
|
obj[3, spam=False] # Valid. index = 3, spam = False, eggs = 2
|
||||||
::
|
obj[spam=False] # Valid. index = (), spam = False, eggs = 2
|
||||||
|
obj[] # Invalid.
|
||||||
# Given type(obj).__getitem__(obj, index, spam=True, eggs=2)
|
|
||||||
obj[3] # Valid. index = 3, spam = True, eggs = 2
|
|
||||||
obj[3, spam=False] # Valid. index = 3, spam = False, eggs = 2
|
|
||||||
obj[spam=False] # Valid. index = (), spam = False, eggs = 2
|
|
||||||
obj[] # Invalid.
|
|
||||||
|
|
||||||
13. The same semantics given above must be extended to ``__class__getitem__``:
|
13. The same semantics given above must be extended to ``__class__getitem__``:
|
||||||
Since PEP 560, type hints are dispatched so that for ``x[y]``, if no
|
Since PEP 560, type hints are dispatched so that for ``x[y]``, if no
|
||||||
|
@ -517,18 +447,14 @@ 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::
|
||||||
|
|
||||||
::
|
def __getitem__(self, index):
|
||||||
|
|
||||||
def __getitem__(self, index):
|
then the caller could call that using keyword syntax, like these two cases::
|
||||||
|
|
||||||
then the caller could call that using keyword syntax, like these two cases:
|
obj[3, index=4]
|
||||||
|
obj[index=1]
|
||||||
::
|
|
||||||
|
|
||||||
obj[3, index=4]
|
|
||||||
obj[index=1]
|
|
||||||
|
|
||||||
The resulting behavior would be an error automatically, since it would be like
|
The resulting behavior would be an error automatically, since it would be like
|
||||||
attempting to call the method with two values for the ``index`` argument, and
|
attempting to call the method with two values for the ``index`` argument, and
|
||||||
|
@ -540,19 +466,14 @@ With the introduction of the new notation, a few corner cases need to be analyse
|
||||||
backward compatibility issues on this respect.
|
backward compatibility issues on this respect.
|
||||||
|
|
||||||
Classes that wish to stress this behavior explicitly can define their
|
Classes that wish to stress this behavior explicitly can define their
|
||||||
parameters as positional-only:
|
parameters as positional-only::
|
||||||
|
|
||||||
::
|
def __getitem__(self, index, /):
|
||||||
|
|
||||||
def __getitem__(self, index, /):
|
2. a similar case occurs with setter notation::
|
||||||
|
|
||||||
2. a similar case occurs with setter notation
|
# Given type(obj).__getitem__(self, index, value):
|
||||||
|
obj[1, value=3] = 5
|
||||||
::
|
|
||||||
|
|
||||||
# Given type(obj).__getitem__(self, index, value):
|
|
||||||
|
|
||||||
obj[1, value=3] = 5
|
|
||||||
|
|
||||||
This poses no issue because the value is passed automatically, and the python interpreter will raise
|
This poses no issue because the value is passed automatically, and the python interpreter will raise
|
||||||
``TypeError: got multiple values for keyword argument 'value'``
|
``TypeError: got multiple values for keyword argument 'value'``
|
||||||
|
@ -560,48 +481,36 @@ With the introduction of the new notation, a few corner cases need to be analyse
|
||||||
|
|
||||||
3. If the subscript dunders are declared to use positional-or-keyword
|
3. If the subscript dunders are declared to use positional-or-keyword
|
||||||
parameters, there may be some surprising cases when arguments are passed
|
parameters, there may be some surprising cases when arguments are passed
|
||||||
to the method. Given the signature:
|
to the method. Given the signature::
|
||||||
|
|
||||||
::
|
def __getitem__(self, index, direction='north')
|
||||||
|
|
||||||
def __getitem__(self, index, direction='north')
|
if the caller uses this::
|
||||||
|
|
||||||
if the caller uses this:
|
obj[0, 'south']
|
||||||
|
|
||||||
::
|
they will probably be surprised by the method call::
|
||||||
|
|
||||||
obj[0, 'south']
|
|
||||||
|
|
||||||
they will probably be surprised by the method call:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
# expected type(obj).__getitem__(0, direction='south')
|
|
||||||
# but actually get:
|
|
||||||
obj.__getitem__((0, 'south'), direction='north')
|
|
||||||
|
|
||||||
|
# expected type(obj).__getitem__(0, direction='south')
|
||||||
|
# but actually get:
|
||||||
|
obj.__getitem__((0, 'south'), direction='north')
|
||||||
|
|
||||||
Solution: best practice suggests that keyword subscripts should be
|
Solution: best practice suggests that keyword subscripts should be
|
||||||
flagged as keyword-only when possible:
|
flagged as keyword-only when possible::
|
||||||
|
|
||||||
::
|
def __getitem__(self, index, *, direction='north')
|
||||||
|
|
||||||
def __getitem__(self, index, *, direction='north')
|
|
||||||
|
|
||||||
The interpreter need not enforce this rule, as there could be scenarios
|
The interpreter need not enforce this rule, as there could be scenarios
|
||||||
where this is the desired behaviour. But linters may choose to warn
|
where this is the desired behaviour. But linters may choose to warn
|
||||||
about subscript methods which don't use the keyword-only flag.
|
about subscript methods which don't use the keyword-only flag.
|
||||||
|
|
||||||
|
|
||||||
4. As we saw, a single value followed by a keyword argument will not be changed into a tuple, i.e.:
|
4. As we saw, a single value followed by a keyword argument will not be changed into a tuple, i.e.:
|
||||||
``d[1, a=3]`` is treated as ``__getitem__(1, a=3)``, NOT ``__getitem__((1,), a=3)``. It would be
|
``d[1, a=3]`` is treated as ``__getitem__(1, a=3)``, NOT ``__getitem__((1,), a=3)``. It would be
|
||||||
extremely confusing if adding keyword arguments were to change the type of the passed index.
|
extremely confusing if adding keyword arguments were to change the type of the passed index.
|
||||||
In other words, adding a keyword to a single-valued subscript will not change it into a tuple.
|
In other words, adding a keyword to a single-valued subscript will not change it into a tuple.
|
||||||
For those cases where an actual tuple needs to be passed, a proper syntax will have to be used:
|
For those cases where an actual tuple needs to be passed, a proper syntax will have to be used::
|
||||||
|
|
||||||
::
|
obj[(1,), a=3] # calls __getitem__((1,), a=3)
|
||||||
|
|
||||||
obj[(1,), a=3] # calls __getitem__((1,), a=3)
|
|
||||||
|
|
||||||
In this case, the call is passing a single element (which is passed as is, as from rule above),
|
In this case, the call is passing a single element (which is passed as is, as from rule above),
|
||||||
only that the single element happens to be a tuple.
|
only that the single element happens to be a tuple.
|
||||||
|
@ -609,31 +518,25 @@ With the introduction of the new notation, a few corner cases need to be analyse
|
||||||
Note that this behavior just reveals the truth that the ``obj[1,]`` notation is shorthand for
|
Note that this behavior just reveals the truth that the ``obj[1,]`` notation is shorthand for
|
||||||
``obj[(1,)]`` (and also ``obj[1]`` is shorthand for ``obj[(1)]``, with the expected behavior).
|
``obj[(1,)]`` (and also ``obj[1]`` is shorthand for ``obj[(1)]``, with the expected behavior).
|
||||||
When keywords are present, the rule that you can omit this outermost pair of parentheses is no
|
When keywords are present, the rule that you can omit this outermost pair of parentheses is no
|
||||||
longer true.
|
longer true::
|
||||||
|
|
||||||
::
|
obj[1] # calls __getitem__(1)
|
||||||
|
obj[1, a=3] # calls __getitem__(1, a=3)
|
||||||
|
obj[1,] # calls __getitem__((1,))
|
||||||
|
obj[(1,), a=3] # calls __getitem__((1,), a=3)
|
||||||
|
|
||||||
obj[1] # calls __getitem__(1)
|
This is particularly relevant in the case where two entries are passed::
|
||||||
obj[1, a=3] # calls __getitem__(1, a=3)
|
|
||||||
obj[1,] # calls __getitem__((1,))
|
|
||||||
obj[(1,), a=3] # calls __getitem__((1,), a=3)
|
|
||||||
|
|
||||||
This is particularly relevant in the case where two entries are passed:
|
obj[1, 2] # calls __getitem__((1, 2))
|
||||||
|
obj[(1, 2)] # same as above
|
||||||
|
obj[1, 2, a=3] # calls __getitem__((1, 2), a=3)
|
||||||
|
obj[(1, 2), a=3] # calls __getitem__((1, 2), a=3)
|
||||||
|
|
||||||
::
|
And particularly when the tuple is extracted as a variable::
|
||||||
|
|
||||||
obj[1, 2] # calls __getitem__((1, 2))
|
t = (1, 2)
|
||||||
obj[(1, 2)] # same as above
|
obj[t] # calls __getitem__((1, 2))
|
||||||
obj[1, 2, a=3] # calls __getitem__((1, 2), a=3)
|
obj[t, a=3] # calls __getitem__((1, 2), a=3)
|
||||||
obj[(1, 2), a=3] # calls __getitem__((1, 2), a=3)
|
|
||||||
|
|
||||||
And particularly when the tuple is extracted as a variable:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
t = (1, 2)
|
|
||||||
obj[t] # calls __getitem__((1, 2))
|
|
||||||
obj[t, a=3] # calls __getitem__((1, 2), a=3)
|
|
||||||
|
|
||||||
Why? because in the case ``obj[1, 2, a=3]`` we are passing two elements (which
|
Why? because in the case ``obj[1, 2, a=3]`` we are passing two elements (which
|
||||||
are then packed as a tuple and passed as the index). In the case ``obj[(1, 2), a=3]``
|
are then packed as a tuple and passed as the index). In the case ``obj[(1, 2), a=3]``
|
||||||
|
@ -663,7 +566,6 @@ to invoke the old functions. We propose ``BINARY_SUBSCR_EX``,
|
||||||
have to generate these new opcodes. The ``PyObject_(Get|Set|Del)Item`` implementations
|
have to generate these new opcodes. The ``PyObject_(Get|Set|Del)Item`` implementations
|
||||||
will call the extended methods passing ``NULL`` as kwargs.
|
will call the extended methods passing ``NULL`` as kwargs.
|
||||||
|
|
||||||
|
|
||||||
Workarounds
|
Workarounds
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -680,68 +582,52 @@ be universal. For example, a module or package might require the use of its own
|
||||||
helpers.
|
helpers.
|
||||||
|
|
||||||
1. User defined classes can be given ``getitem`` and ``delitem`` methods,
|
1. User defined classes can be given ``getitem`` and ``delitem`` methods,
|
||||||
that respectively get and delete values stored in a container.
|
that respectively get and delete values stored in a container::
|
||||||
|
|
||||||
::
|
>>> val = x.getitem(1, 2, a=3, b=4)
|
||||||
|
>>> x.delitem(1, 2, a=3, b=4)
|
||||||
|
|
||||||
>>> val = x.getitem(1, 2, a=3, b=4)
|
The same can't be done for ``setitem``. It's not valid syntax::
|
||||||
>>> x.delitem(1, 2, a=3, b=4)
|
|
||||||
|
|
||||||
The same can't be done for ``setitem``. It's not valid syntax.
|
>>> x.setitem(1, 2, a=3, b=4) = val
|
||||||
|
SyntaxError: can't assign to function call
|
||||||
::
|
|
||||||
|
|
||||||
>>> x.setitem(1, 2, a=3, b=4) = val
|
|
||||||
SyntaxError: can't assign to function call
|
|
||||||
|
|
||||||
2. A helper class, here called ``H``, can be used to swap the container
|
2. A helper class, here called ``H``, can be used to swap the container
|
||||||
and parameter roles. In other words, we use
|
and parameter roles. In other words, we use::
|
||||||
|
|
||||||
::
|
H(1, 2, a=3, b=4)[x]
|
||||||
|
|
||||||
H(1, 2, a=3, b=4)[x]
|
as a substitute for::
|
||||||
|
|
||||||
as a substitute for
|
x[1, 2, a=3, b=4]
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
x[1, 2, a=3, b=4]
|
|
||||||
|
|
||||||
This method will work for ``getitem``, ``delitem`` and also for
|
This method will work for ``getitem``, ``delitem`` and also for
|
||||||
``setitem``. This is because
|
``setitem``. This is because::
|
||||||
|
|
||||||
::
|
>>> H(1, 2, a=3, b=4)[x] = val
|
||||||
|
|
||||||
>>> H(1, 2, a=3, b=4)[x] = val
|
|
||||||
|
|
||||||
is valid syntax, which can be given the appropriate semantics.
|
is valid syntax, which can be given the appropriate semantics.
|
||||||
|
|
||||||
3. A helper function, here called ``P``, can be used to store the
|
3. A helper function, here called ``P``, can be used to store the
|
||||||
arguments in a single object. For example
|
arguments in a single object. For example::
|
||||||
|
|
||||||
::
|
>>> x[P(1, 2, a=3, b=4)] = val
|
||||||
|
|
||||||
>>> x[P(1, 2, a=3, b=4)] = val
|
|
||||||
|
|
||||||
is valid syntax, and can be given the appropriate semantics.
|
is valid syntax, and can be given the appropriate semantics.
|
||||||
|
|
||||||
4. The ``lo:hi:step`` syntax for slices is sometimes very useful. This
|
4. The ``lo:hi:step`` syntax for slices is sometimes very useful. This
|
||||||
syntax is not directly available in the work-arounds. However
|
syntax is not directly available in the work-arounds. However::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
s[lo:hi:step]
|
s[lo:hi:step]
|
||||||
|
|
||||||
provides a work-around that is available everything, where
|
provides a work-around that is available everything, where::
|
||||||
|
|
||||||
::
|
class S:
|
||||||
|
def __getitem__(self, key): return key
|
||||||
|
|
||||||
|
s = S()
|
||||||
|
|
||||||
class S:
|
defines the helper object ``s``.
|
||||||
def __getitem__(self, key): return key
|
|
||||||
|
|
||||||
s = S()
|
|
||||||
|
|
||||||
defines the helper object `s`.
|
|
||||||
|
|
||||||
Rejected Ideas
|
Rejected Ideas
|
||||||
==============
|
==============
|
||||||
|
@ -771,19 +657,15 @@ that are invoked over the ``__(get|set|del)item__`` triad, if they are present.
|
||||||
|
|
||||||
The rationale around this choice is to make the intuition around how to add kwd
|
The rationale around this choice is to make the intuition around how to add kwd
|
||||||
arg support to square brackets more obvious and in line with the function
|
arg support to square brackets more obvious and in line with the function
|
||||||
behavior. Given:
|
behavior. Given::
|
||||||
|
|
||||||
::
|
def __getitem_ex__(self, x, y): ...
|
||||||
|
|
||||||
def __getitem_ex__(self, x, y): ...
|
These all just work and produce the same result effortlessly::
|
||||||
|
|
||||||
These all just work and produce the same result effortlessly:
|
obj[1, 2]
|
||||||
|
obj[1, y=2]
|
||||||
::
|
obj[y=2, x=1]
|
||||||
|
|
||||||
obj[1, 2]
|
|
||||||
obj[1, y=2]
|
|
||||||
obj[y=2, x=1]
|
|
||||||
|
|
||||||
In other words, this solution would unify the behavior of ``__getitem__`` to the traditional
|
In other words, this solution would unify the behavior of ``__getitem__`` to the traditional
|
||||||
function signature, but since we can't change ``__getitem__`` and break backward compatibility,
|
function signature, but since we can't change ``__getitem__`` and break backward compatibility,
|
||||||
|
@ -813,14 +695,12 @@ The problems with this approach were found to be:
|
||||||
- it would potentially lead to mixed situations where the extended version is
|
- it would potentially lead to mixed situations where the extended version is
|
||||||
defined for the getter, but not for the setter.
|
defined for the getter, but not for the setter.
|
||||||
|
|
||||||
- In the __setitem_ex__ signature, value would have to be made the first
|
- In the ``__setitem_ex__`` signature, value would have to be made the first
|
||||||
element, because the index is of arbitrary length depending on the specified
|
element, because the index is of arbitrary length depending on the specified
|
||||||
indexes. This would look awkward because the visual notation does not match
|
indexes. This would look awkward because the visual notation does not match
|
||||||
the signature:
|
the signature::
|
||||||
|
|
||||||
::
|
obj[1, 2] = 3 # calls obj.__setitem_ex__(3, 1, 2)
|
||||||
|
|
||||||
obj[1, 2] = 3 # calls obj.__setitem_ex__(3, 1, 2)
|
|
||||||
|
|
||||||
- the solution relies on the assumption that all keyword indices necessarily map
|
- the solution relies on the assumption that all keyword indices necessarily map
|
||||||
into positional indices, or that they must have a name. This assumption may be
|
into positional indices, or that they must have a name. This assumption may be
|
||||||
|
@ -842,11 +722,9 @@ Has problems similar to the above.
|
||||||
create a new "kwslice" object
|
create a new "kwslice" object
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
This proposal has already been explored in "New arguments contents" P4 in PEP 472.
|
This proposal has already been explored in "New arguments contents" P4 in PEP 472::
|
||||||
|
|
||||||
::
|
obj[a, b:c, x=1] # calls __getitem__(a, slice(b, c), key(x=1))
|
||||||
|
|
||||||
obj[a, b:c, x=1] # calls __getitem__(a, slice(b, c), key(x=1))
|
|
||||||
|
|
||||||
This solution requires everyone who needs keyword arguments to parse the tuple
|
This solution requires everyone who needs keyword arguments to parse the tuple
|
||||||
and/or key object by hand to extract them. This is painful and opens up to the
|
and/or key object by hand to extract them. This is painful and opens up to the
|
||||||
|
@ -858,24 +736,18 @@ make sense and which ones do not.
|
||||||
Using a single bit to change the behavior
|
Using a single bit to change the behavior
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
A special class dunder flag
|
A special class dunder flag::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
__keyfn__ = True
|
__keyfn__ = True
|
||||||
|
|
||||||
would change the signature of the ``__get|set|delitem__`` to a "function like" dispatch,
|
would change the signature of the ``__get|set|delitem__`` to a "function like" dispatch,
|
||||||
meaning that this
|
meaning that this::
|
||||||
|
|
||||||
::
|
>>> d[1, 2, z=3]
|
||||||
|
|
||||||
>>> d[1, 2, z=3]
|
would result in a call to::
|
||||||
|
|
||||||
would result in a call to
|
>>> d.__getitem__(1, 2, z=3) # instead of d.__getitem__((1, 2), z=3)
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
>>> d.__getitem__(1, 2, z=3) # instead of d.__getitem__((1, 2), z=3)
|
|
||||||
|
|
||||||
This option has been rejected because it feels odd that a signature of a method
|
This option has been rejected because it feels odd that a signature of a method
|
||||||
depends on a specific value of another dunder. It would be confusing for both
|
depends on a specific value of another dunder. It would be confusing for both
|
||||||
|
@ -916,47 +788,37 @@ Use None instead of the empty tuple when no positional index is given
|
||||||
The case ``obj[k=3]`` will lead to a call ``__getitem__((), k=3)``.
|
The case ``obj[k=3]`` will lead to a call ``__getitem__((), k=3)``.
|
||||||
The alternative ``__getitem__(None, k=3)`` was considered but rejected:
|
The alternative ``__getitem__(None, k=3)`` was considered but rejected:
|
||||||
NumPy uses `None` to indicate inserting a new axis/dimensions (there's
|
NumPy uses `None` to indicate inserting a new axis/dimensions (there's
|
||||||
a ``np.newaxis`` alias as well):
|
a ``np.newaxis`` alias as well)::
|
||||||
|
|
||||||
::
|
arr = np.array(5)
|
||||||
|
arr.ndim == 0
|
||||||
|
arr[None].ndim == arr[None,].ndim == 1
|
||||||
|
|
||||||
arr = np.array(5)
|
So the final conclusion is that we favor the following series::
|
||||||
arr.ndim == 0
|
|
||||||
arr[None].ndim == arr[None,].ndim == 1
|
|
||||||
|
|
||||||
So the final conclusion is that we favor the following series:
|
obj[k=3] # __getitem__((), k=3). Empty tuple
|
||||||
|
obj[1, k=3] # __getitem__(1, k=3). Integer
|
||||||
|
obj[1, 2, k=3] # __getitem__((1, 2), k=3). Tuple
|
||||||
|
|
||||||
::
|
more than this::
|
||||||
|
|
||||||
obj[k=3] # __getitem__((), k=3). Empty tuple
|
obj[k=3] # __getitem__(None, k=3). None
|
||||||
obj[1, k=3] # __getitem__(1, k=3). Integer
|
obj[1, k=3] # __getitem__(1, k=3). Integer
|
||||||
obj[1, 2, k=3] # __getitem__((1, 2), k=3). Tuple
|
obj[1, 2, k=3] # __getitem__((1, 2), k=3). Tuple
|
||||||
|
|
||||||
more than this:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
obj[k=3] # __getitem__(None, k=3). None
|
|
||||||
obj[1, k=3] # __getitem__(1, k=3). Integer
|
|
||||||
obj[1, 2, k=3] # __getitem__((1, 2), k=3). Tuple
|
|
||||||
|
|
||||||
With the first more in line with a \*args semantics for calling a routine with
|
With the first more in line with a \*args semantics for calling a routine with
|
||||||
no positional arguments
|
no positional arguments::
|
||||||
|
|
||||||
::
|
>>> def foo(*args, **kwargs):
|
||||||
|
... print(args, kwargs)
|
||||||
|
...
|
||||||
|
>>> foo(k=3)
|
||||||
|
() {'k': 3}
|
||||||
|
|
||||||
>>> def foo(*args, **kwargs):
|
Although we accept the following asymmetry::
|
||||||
... print(args, kwargs)
|
|
||||||
...
|
|
||||||
>>> foo(k=3)
|
|
||||||
() {'k': 3}
|
|
||||||
|
|
||||||
Although we accept the following asymmetry:
|
>>> foo(1, k=3)
|
||||||
|
(1,) {'k': 3}
|
||||||
::
|
|
||||||
|
|
||||||
>>> foo(1, k=3)
|
|
||||||
(1,) {'k': 3}
|
|
||||||
|
|
||||||
|
|
||||||
Common objections
|
Common objections
|
||||||
|
@ -971,18 +833,14 @@ Common objections
|
||||||
One problem is type hint creation has been extended to built-ins in python 3.9,
|
One problem is type hint creation has been extended to built-ins in python 3.9,
|
||||||
so that you do not have to import Dict, List, et al anymore.
|
so that you do not have to import Dict, List, et al anymore.
|
||||||
|
|
||||||
Without kwdargs inside ``[]``, you would not be able to do this:
|
Without kwdargs inside ``[]``, you would not be able to do this::
|
||||||
|
|
||||||
::
|
Vector = dict[i=float, j=float]
|
||||||
|
|
||||||
Vector = dict[i=float, j=float]
|
|
||||||
|
|
||||||
but for obvious reasons, call syntax using builtins to create custom type hints
|
but for obvious reasons, call syntax using builtins to create custom type hints
|
||||||
isn't an option:
|
isn't an option::
|
||||||
|
|
||||||
::
|
dict(i=float, j=float) # would create a dictionary, not a type
|
||||||
|
|
||||||
dict(i=float, j=float) # would create a dictionary, not a type
|
|
||||||
|
|
||||||
References
|
References
|
||||||
==========
|
==========
|
Loading…
Reference in New Issue