@@ -31,7 +31,10 @@ to indexing operations::
31
31
>>> x[1, 2, a=3, b=4] = val # setitem
32
32
>>> del x[1, 2, a=3, b=4] # delitem
33
33
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.
35
38
36
39
This PEP is a successor to PEP 472, which was rejected due to lack of
37
40
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:
122
125
arguments) need some way to determine which arguments are destined for
123
126
the target function, and which are used to configure how they run the
124
127
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 ]_
126
129
127
130
An indexed notation would afford a Pythonic way to pass keyword
128
131
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
464
467
accept keyword arguments. In other words, if ``d `` is a ``dict ``, the
465
468
statement ``d[1, a=2] `` will raise ``TypeError ``, as their implementation will
466
469
not support the use of keyword arguments. The same holds for all other classes
467
- (list, frozendict , etc.)
470
+ (list, dict , etc.)
468
471
469
472
Corner case and Gotchas
470
473
-----------------------
@@ -496,7 +499,7 @@ With the introduction of the new notation, a few corner cases need to be analyse
496
499
497
500
2. a similar case occurs with setter notation::
498
501
499
- # Given type(obj).__setitem__(self , index, value):
502
+ # Given type(obj).__setitem__(obj , index, value):
500
503
obj[1, value=3] = 5
501
504
502
505
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
515
518
516
519
they will probably be surprised by the method call::
517
520
518
- # expected type(obj).__getitem__(0, direction='south')
521
+ # expected type(obj).__getitem__(obj, 0, direction='south')
519
522
# but actually get:
520
- obj.__getitem__((0, 'south'), direction='north')
523
+ type( obj) .__getitem__(obj, (0, 'south'), direction='north')
521
524
522
525
Solution: best practice suggests that keyword subscripts should be
523
526
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
529
532
about subscript methods which don't use the keyword-only flag.
530
533
531
534
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
533
536
extremely confusing if adding keyword arguments were to change the type of the passed index.
534
537
In other words, adding a keyword to a single-valued subscript will not change it into a tuple.
535
538
For those cases where an actual tuple needs to be passed, a proper syntax will have to be used::
536
539
537
- obj[(1,), a=3] # calls __getitem__((1,), a=3)
540
+ obj[(1,), a=3]
541
+ # calls type(obj).__getitem__(obj, (1,), a=3)
538
542
539
543
In this case, the call is passing a single element (which is passed as is, as from rule above),
540
544
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
544
548
When keywords are present, the rule that you can omit this outermost pair of parentheses is no
545
549
longer true::
546
550
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)
551
562
552
563
This is particularly relevant in the case where two entries are passed::
553
564
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)
558
576
559
577
And particularly when the tuple is extracted as a variable::
560
578
561
579
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)
564
585
565
586
Why? because in the case ``obj[1, 2, a=3] `` we are passing two elements (which
566
587
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
593
614
old C implementations will call the extended methods passing ``NULL ``
594
615
as kwargs.
595
616
617
+ Reference Implementation
618
+ ========================
619
+
620
+ A reference implementation is currently being developed here [#reference-impl ]_.
621
+
622
+
596
623
Workarounds
597
624
===========
598
625
@@ -663,8 +690,7 @@ Previous PEP 472 solutions
663
690
--------------------------
664
691
665
692
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:
668
694
669
695
I have now carefully read through PEP 472 in full, and I am afraid I
670
696
cannot support any of the strategies currently in the PEP.
@@ -727,7 +753,8 @@ The problems with this approach were found to be:
727
753
indexes. This would look awkward because the visual notation does not match
728
754
the signature::
729
755
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)
731
758
732
759
- the solution relies on the assumption that all keyword indices necessarily map
733
760
into positional indices, or that they must have a name. This assumption may be
@@ -751,7 +778,8 @@ create a new "kwslice" object
751
778
752
779
This proposal has already been explored in "New arguments contents" P4 in PEP 472::
753
780
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))
755
783
756
784
This solution requires everyone who needs keyword arguments to parse the tuple
757
785
and/or key object by hand to extract them. This is painful and opens up to the
@@ -774,7 +802,8 @@ meaning that this::
774
802
775
803
would result in a call to::
776
804
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)
778
807
779
808
This option has been rejected because it feels odd that a signature of a method
780
809
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
829
858
opens up the question of what entity to pass for the index position when no index
830
859
is passed::
831
860
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)
833
863
834
864
A proposed hack would be to let the user specify which entity to use when an
835
865
index is not specified, by specifying a default for the ``index ``, but this
836
866
forces necessarily to also specify a (never going to be used, as a value is
837
867
always passed by design) default for the ``value ``, as we can't have
838
868
non-default arguments after defaulted one::
839
869
840
- def __setitem__(index=SENTINEL, value=NEVERUSED, *, k)
870
+ def __setitem__(self, index=SENTINEL, value=NEVERUSED, *, k)
841
871
842
872
which seems ugly, redundant and confusing. We must therefore accept that some
843
873
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``::
856
886
857
887
expecting a call like::
858
888
859
- obj.__setitem__(SENTINEL, 0, **{"value": 1})
889
+ type( obj) .__setitem__(obj, SENTINEL, 0, **{"value": 1})
860
890
861
891
will instead accidentally be catched by the named ``value ``, producing a
862
892
``duplicate value error ``. The user should not be worried about the actual
@@ -890,7 +920,7 @@ the options were:
890
920
891
921
For option 1, the call will become::
892
922
893
- type(obj).__getitem__((), k=3)
923
+ type(obj).__getitem__(obj, (), k=3)
894
924
895
925
therefore making ``obj[k=3] `` and ``obj[(), k=3] `` degenerate and indistinguishable.
896
926
@@ -928,8 +958,8 @@ the two degenerate cases.
928
958
929
959
So, an alternative strategy (option 3) would be to use an existing entity that is
930
960
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
933
963
should be attempted (e.g. to ask the other object). Unfortunately, its name and
934
964
traditional use calls back to a feature that is not available, rather than the
935
965
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
943
973
it's not crucial, and the empty tuple is an acceptable option. Hence the
944
974
resulting series will be::
945
975
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
949
984
950
985
and the following two notation will be degenerate::
951
986
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)
954
992
955
993
Common objections
956
994
=================
@@ -971,7 +1009,8 @@ Common objections
971
1009
but for obvious reasons, call syntax using builtins to create custom type hints
972
1010
isn't an option::
973
1011
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
975
1014
976
1015
Finally, function calls do not allow for a setitem-like notation, as shown
977
1016
in the Overview: operations such as ``f(1, x=3) = 5 `` are not allowed, and are
@@ -995,6 +1034,8 @@ References
995
1034
(https://github.com/python-trio/trio/issues/470)
996
1035
.. [#numpy-ml ] "[Numpy-discussion] Request for comments on PEP 637 - Support for indexing with keyword arguments"
997
1036
(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)
998
1039
999
1040
Copyright
1000
1041
=========
0 commit comments