Skip to content

Commit fc43230

Browse files
PEP 637: Feedback and various typo fixes (GH-1741)
1 parent c86d1cc commit fc43230

File tree

1 file changed

+76
-35
lines changed

1 file changed

+76
-35
lines changed

pep-0637.rst

+76-35
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ to indexing operations::
3131
>>> x[1, 2, a=3, b=4] = val # setitem
3232
>>> del x[1, 2, a=3, b=4] # delitem
3333

34-
and would also provide appropriate semantics.
34+
and would also provide appropriate semantics. Single- and double-star unpacking of
35+
arguments is also provided::
36+
37+
>>> val = x[*(1, 2), **{a=3, b=4}] # Equivalent to above.
3538

3639
This PEP is a successor to PEP 472, which was rejected due to lack of
3740
interest in 2019. Since then there's been renewed interest in the feature.
@@ -122,7 +125,7 @@ specification would improve notation and provide additional value:
122125
arguments) need some way to determine which arguments are destined for
123126
the target function, and which are used to configure how they run the
124127
target. This is simple (if non-extensible) for positional parameters,
125-
but we need some way to distinguish these for keywords.[#trio-run]_
128+
but we need some way to distinguish these for keywords. [#trio-run]_
126129

127130
An indexed notation would afford a Pythonic way to pass keyword
128131
arguments to these functions without cluttering the caller's code.
@@ -464,7 +467,7 @@ classes with indexing semantics) will remain the same and will continue not to
464467
accept keyword arguments. In other words, if ``d`` is a ``dict``, the
465468
statement ``d[1, a=2]`` will raise ``TypeError``, as their implementation will
466469
not support the use of keyword arguments. The same holds for all other classes
467-
(list, frozendict, etc.)
470+
(list, dict, etc.)
468471

469472
Corner case and Gotchas
470473
-----------------------
@@ -496,7 +499,7 @@ With the introduction of the new notation, a few corner cases need to be analyse
496499

497500
2. a similar case occurs with setter notation::
498501

499-
# Given type(obj).__setitem__(self, index, value):
502+
# Given type(obj).__setitem__(obj, index, value):
500503
obj[1, value=3] = 5
501504

502505
This poses no issue because the value is passed automatically, and the python interpreter will raise
@@ -515,9 +518,9 @@ With the introduction of the new notation, a few corner cases need to be analyse
515518

516519
they will probably be surprised by the method call::
517520

518-
# expected type(obj).__getitem__(0, direction='south')
521+
# expected type(obj).__getitem__(obj, 0, direction='south')
519522
# but actually get:
520-
obj.__getitem__((0, 'south'), direction='north')
523+
type(obj).__getitem__(obj, (0, 'south'), direction='north')
521524

522525
Solution: best practice suggests that keyword subscripts should be
523526
flagged as keyword-only when possible::
@@ -529,12 +532,13 @@ With the introduction of the new notation, a few corner cases need to be analyse
529532
about subscript methods which don't use the keyword-only flag.
530533

531534
4. As we saw, a single value followed by a keyword argument will not be changed into a tuple, i.e.:
532-
``d[1, a=3]`` is treated as ``__getitem__(1, a=3)``, NOT ``__getitem__((1,), a=3)``. It would be
535+
``d[1, a=3]`` is treated as ``__getitem__(d, 1, a=3)``, NOT ``__getitem__(d, (1,), a=3)``. It would be
533536
extremely confusing if adding keyword arguments were to change the type of the passed index.
534537
In other words, adding a keyword to a single-valued subscript will not change it into a tuple.
535538
For those cases where an actual tuple needs to be passed, a proper syntax will have to be used::
536539

537-
obj[(1,), a=3] # calls __getitem__((1,), a=3)
540+
obj[(1,), a=3]
541+
# calls type(obj).__getitem__(obj, (1,), a=3)
538542

539543
In this case, the call is passing a single element (which is passed as is, as from rule above),
540544
only that the single element happens to be a tuple.
@@ -544,23 +548,40 @@ With the introduction of the new notation, a few corner cases need to be analyse
544548
When keywords are present, the rule that you can omit this outermost pair of parentheses is no
545549
longer true::
546550

547-
obj[1] # calls __getitem__(1)
548-
obj[1, a=3] # calls __getitem__(1, a=3)
549-
obj[1,] # calls __getitem__((1,))
550-
obj[(1,), a=3] # calls __getitem__((1,), a=3)
551+
obj[1]
552+
# calls type(obj).__getitem__(obj, 1)
553+
554+
obj[1, a=3]
555+
# calls type(obj).__getitem__(obj, 1, a=3)
556+
557+
obj[1,]
558+
# calls type(obj).__getitem__(obj, (1,))
559+
560+
obj[(1,), a=3]
561+
# calls type(obj).__getitem__(obj, (1,), a=3)
551562

552563
This is particularly relevant in the case where two entries are passed::
553564

554-
obj[1, 2] # calls __getitem__((1, 2))
555-
obj[(1, 2)] # same as above
556-
obj[1, 2, a=3] # calls __getitem__((1, 2), a=3)
557-
obj[(1, 2), a=3] # calls __getitem__((1, 2), a=3)
565+
obj[1, 2]
566+
# calls type(obj).__getitem__(obj, (1, 2))
567+
568+
obj[(1, 2)]
569+
# same as above
570+
571+
obj[1, 2, a=3]
572+
# calls type(obj).__getitem__(obj, (1, 2), a=3)
573+
574+
obj[(1, 2), a=3]
575+
# calls type(obj).__getitem__(obj, (1, 2), a=3)
558576

559577
And particularly when the tuple is extracted as a variable::
560578

561579
t = (1, 2)
562-
obj[t] # calls __getitem__((1, 2))
563-
obj[t, a=3] # calls __getitem__((1, 2), a=3)
580+
obj[t]
581+
# calls type(obj).__getitem__(obj, (1, 2))
582+
583+
obj[t, a=3]
584+
# calls type(obj).__getitem__(obj, (1, 2), a=3)
564585

565586
Why? because in the case ``obj[1, 2, a=3]`` we are passing two elements (which
566587
are then packed as a tuple and passed as the index). In the case ``obj[(1, 2), a=3]``
@@ -593,6 +614,12 @@ compiler will have to generate these new opcodes. The
593614
old C implementations will call the extended methods passing ``NULL``
594615
as kwargs.
595616

617+
Reference Implementation
618+
========================
619+
620+
A reference implementation is currently being developed here [#reference-impl]_.
621+
622+
596623
Workarounds
597624
===========
598625

@@ -663,8 +690,7 @@ Previous PEP 472 solutions
663690
--------------------------
664691

665692
PEP 472 presents a good amount of ideas that are now all to be considered
666-
Rejected. A personal email from D'Aprano to one of the authors (Stefano Borini)
667-
specifically said:
693+
Rejected. A personal email from D'Aprano to the author specifically said:
668694

669695
I have now carefully read through PEP 472 in full, and I am afraid I
670696
cannot support any of the strategies currently in the PEP.
@@ -727,7 +753,8 @@ The problems with this approach were found to be:
727753
indexes. This would look awkward because the visual notation does not match
728754
the signature::
729755

730-
obj[1, 2] = 3 # calls obj.__setitem_ex__(3, 1, 2)
756+
obj[1, 2] = 3
757+
# calls type(obj).__setitem_ex__(obj, 3, 1, 2)
731758

732759
- the solution relies on the assumption that all keyword indices necessarily map
733760
into positional indices, or that they must have a name. This assumption may be
@@ -751,7 +778,8 @@ create a new "kwslice" object
751778

752779
This proposal has already been explored in "New arguments contents" P4 in PEP 472::
753780

754-
obj[a, b:c, x=1] # calls __getitem__(a, slice(b, c), key(x=1))
781+
obj[a, b:c, x=1]
782+
# calls type(obj).__getitem__(obj, a, slice(b, c), key(x=1))
755783

756784
This solution requires everyone who needs keyword arguments to parse the tuple
757785
and/or key object by hand to extract them. This is painful and opens up to the
@@ -774,7 +802,8 @@ meaning that this::
774802

775803
would result in a call to::
776804

777-
>>> d.__getitem__(1, 2, z=3) # instead of d.__getitem__((1, 2), z=3)
805+
>>> type(obj).__getitem__(obj, 1, 2, z=3)
806+
# instead of type(obj).__getitem__(obj, (1, 2), z=3)
778807

779808
This option has been rejected because it feels odd that a signature of a method
780809
depends on a specific value of another dunder. It would be confusing for both
@@ -829,15 +858,16 @@ The above consideration makes it impossible to have a keyword only dunder, and
829858
opens up the question of what entity to pass for the index position when no index
830859
is passed::
831860

832-
obj[k=3] = 5 # would call type(obj).__setitem__(???, 5, k=3)
861+
obj[k=3] = 5
862+
# would call type(obj).__setitem__(obj, ???, 5, k=3)
833863

834864
A proposed hack would be to let the user specify which entity to use when an
835865
index is not specified, by specifying a default for the ``index``, but this
836866
forces necessarily to also specify a (never going to be used, as a value is
837867
always passed by design) default for the ``value``, as we can't have
838868
non-default arguments after defaulted one::
839869

840-
def __setitem__(index=SENTINEL, value=NEVERUSED, *, k)
870+
def __setitem__(self, index=SENTINEL, value=NEVERUSED, *, k)
841871

842872
which seems ugly, redundant and confusing. We must therefore accept that some
843873
form of sentinel index must be passed by the python implementation when the
@@ -856,7 +886,7 @@ and a user that wants to pass a keyword ``value``::
856886

857887
expecting a call like::
858888

859-
obj.__setitem__(SENTINEL, 0, **{"value": 1})
889+
type(obj).__setitem__(obj, SENTINEL, 0, **{"value": 1})
860890

861891
will instead accidentally be catched by the named ``value``, producing a
862892
``duplicate value error``. The user should not be worried about the actual
@@ -890,7 +920,7 @@ the options were:
890920

891921
For option 1, the call will become::
892922

893-
type(obj).__getitem__((), k=3)
923+
type(obj).__getitem__(obj, (), k=3)
894924

895925
therefore making ``obj[k=3]`` and ``obj[(), k=3]`` degenerate and indistinguishable.
896926

@@ -928,8 +958,8 @@ the two degenerate cases.
928958

929959
So, an alternative strategy (option 3) would be to use an existing entity that is
930960
unlikely to be used as a valid index. One option could be the current built-in constant
931-
``NotImplemented``, which is currently returned by comparison operators to
932-
report that they do not implement the comparison, and a different strategy
961+
``NotImplemented``, which is currently returned by operators methods to
962+
report that they do not implement a particular operation, and a different strategy
933963
should be attempted (e.g. to ask the other object). Unfortunately, its name and
934964
traditional use calls back to a feature that is not available, rather than the
935965
fact that something was not passed by the user.
@@ -943,14 +973,22 @@ From a quick inquire, it seems that most people on python-ideas seem to believe
943973
it's not crucial, and the empty tuple is an acceptable option. Hence the
944974
resulting series will be::
945975

946-
obj[k=3] # __getitem__((), k=3). Empty tuple
947-
obj[1, k=3] # __getitem__(1, k=3). Integer
948-
obj[1, 2, k=3] # __getitem__((1, 2), k=3). Tuple
976+
obj[k=3]
977+
# type(obj).__getitem__(obj, (), k=3). Empty tuple
978+
979+
obj[1, k=3]
980+
# type(obj).__getitem__(obj, 1, k=3). Integer
981+
982+
obj[1, 2, k=3]
983+
# type(obj).__getitem__(obj, (1, 2), k=3). Tuple
949984

950985
and the following two notation will be degenerate::
951986

952-
obj[(), k=3] # __getitem__((), k=3)
953-
obj[k=3] # __getitem__((), k=3)
987+
obj[(), k=3]
988+
# type(obj).__getitem__(obj, (), k=3)
989+
990+
obj[k=3]
991+
# type(obj).__getitem__(obj, (), k=3)
954992

955993
Common objections
956994
=================
@@ -971,7 +1009,8 @@ Common objections
9711009
but for obvious reasons, call syntax using builtins to create custom type hints
9721010
isn't an option::
9731011

974-
dict(i=float, j=float) # would create a dictionary, not a type
1012+
dict(i=float, j=float)
1013+
# would create a dictionary, not a type
9751014

9761015
Finally, function calls do not allow for a setitem-like notation, as shown
9771016
in the Overview: operations such as ``f(1, x=3) = 5`` are not allowed, and are
@@ -995,6 +1034,8 @@ References
9951034
(https://github.com/python-trio/trio/issues/470)
9961035
.. [#numpy-ml] "[Numpy-discussion] Request for comments on PEP 637 - Support for indexing with keyword arguments"
9971036
(http://numpy-discussion.10968.n7.nabble.com/Request-for-comments-on-PEP-637-Support-for-indexing-with-keyword-arguments-td48489.html)
1037+
.. [#reference-impl] "Reference implementation"
1038+
(https://github.com/python/cpython/compare/master...stefanoborini:PEP-637-implementation-attempt-2)
9981039
9991040
Copyright
10001041
=========

0 commit comments

Comments
 (0)