This is slightly revised version of something I sent to Stefano two weeks ago. I hope he is planning to use this, or something similar, in the PEP, but for what it's worth here it is for discussion. This is, as far as I can tell, the minimum language change needed to support keywords in subscripts, and it will support all the desired use-cases. * * * (1) An empty subscript is still illegal, regardless of context. obj[] # SyntaxError (2) A single subscript remains a single argument: obj[index] # calls type(obj).__getitem__(index) obj[index] = value # calls type(obj).__setitem__(index, value) del obj[index] # calls type(obj).__delitem__(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 a single positional argument: obj[spam, eggs] # calls type(obj).__getitem__((spam, eggs)) obj[spam, eggs] = value # calls type(obj).__setitem__((spam, eggs), value) del obj[spam, eggs] # calls type(obj).__delitem__((spam, eggs)) Points (1) to (3) mean that classes which do not want to support keyword arguments in subscripts need do nothing at all. (Completely backwards compatible.) (4) Keyword arguments, if any, must follow positional arguments. obj[1, 2, spam=None, 3) # SyntaxError This is like function calls, where intermixing positional and keyword arguments give a SyntaxError. (5) Keyword subscripts, if any, will be handled like they are in function calls. Examples: # Single index with keywords: obj[index, spam=1, eggs=2] # calls type(obj).__getitem__(index, spam=1, eggs=2) obj[index, spam=1, eggs=2] = value # calls type(obj).__setitem__(index, value, spam=1, eggs=2) del obj[index, spam=1, eggs=2] # calls type(obj).__delitem__(index, spam=1, eggs=2) # Comma-separated indices with keywords: obj[foo, bar, spam=1, eggs=2] # calls type(obj).__getitem__((foo, bar), spam=1, eggs=2) and *mutatis mutandis* for the set and del cases. (6) The same rules apply with respect to keyword subscripts as for keywords in function calls: - the interpeter matches up each keyword subscript to a named parameter in the appropriate method; - if a named parameter is used twice, that is an error; - if there are any named parameters left over (without a value) when the keywords are all used, they are assigned their default value (if any); - if any such parameter doesn't have a default, that is an error; - if there are any keyword subscripts remaining after all the named parameters are filled, and the method has a `**kwargs` parameter, they are bound to the `**kwargs` parameter as a dict; - but if no `**kwargs` parameter is defined, it is an error. (7) Sequence unpacking remains a syntax error inside subscripts: obj[*items] Reason: unpacking items would result it being immediately repacked into a tuple. Anyone using sequence unpacking in the subscript is probably confused as to what is happening, and it is best if they receive an immediate syntax error with an informative error message. (8) Dict unpacking is permitted: items = {'spam': 1, 'eggs': 2} obj[index, **items] # equivalent to obj[index, spam=1, eggs=2] (9) Keyword-only subscripts are permitted: obj[spam=1, eggs=2] # calls type(obj).__getitem__(spam=1, eggs=2) del obj[spam=1, eggs=2] # calls type(obj).__delitem__(spam=1, eggs=2) but note that the setter is awkward since the signature requires the first parameter: obj[spam=1, eggs=2] = value # wants to call type(obj).__setitem__(???, value, spam=1, eggs=2) Proposed solution: this is a runtime error unless the setitem method gives the first parameter a default, e.g.: def __setitem__(self, index=None, value=None, **kwargs) Note that the second parameter will always be present, nevertheless, to satisfy the interpreter, it too will require a default value. (Editorial comment: this is undoubtably an awkward and ugly corner case, but I am reluctant to prohibit keyword-only assignment.) Comments -------- (a) Non-keyword subscripts are treated the same as the status quo, giving full backwards compatibility. (b) Technically, if a class defines their getter like this: def __getitem__(self, index): then the caller could call that using keyword syntax: obj[index=1] but this should be harmless with no behavioural difference. But classes that wish to avoid this can define their parameters as positional-only: def __getitem__(self, index, /): (c) If the method is declared with no positional arguments (aside from self), only keyword subscripts can be given: def __getitem__(self, *, index) # requires obj[index=1] not obj[1] Although this is unavoidably awkward for setters: # Intent is for the object to only support keyword subscripts. def __setitem__(self, i=None, value=None, /, *, index) if i is not None: raise TypeError('only keyword arguments permitted') Gotchas ------- If the subscript dunders are declared to use positional-or-keyword parameters, there may be some surprising cases when arguments are passed to the method. Given the signature: def __getitem__(self, index, direction='north') if the caller uses this: 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') Solution: best practice suggests that keyword subscripts should be flagged as keyword-only when possible: def __getitem__(self, index, *, direction='north') The interpreter need not enforce this rule, as there could be scenarios where this is the desired behaviour. But linters may choose to warn about subscript methods which don't use the keyword-only flag. -- Steve