I would like to propose dict (or mapping) unpacking assignment. This is inspired in part by the Python-Ideas thread "f-strings as assignment targets", dict unpacking in function calls, and iterable unpacking assignment.
Comments welcome. Background ---------- Iterable unpacking assignment: values = (1, 2, 3) a, b, c = *values is a very successful and powerful technique in Python. Likewise dict unpacking in function calls and dict displays: kwargs = {'a': 1, 'b': 2} func(**kwargs) d = {**kwargs, 'c': 3} There have been various requests for allowing dict unpacking on the right-hand side of an assignment [citation required] but in my opinion none of them have had a good justification. Motivated by the idea of scanning text strings with a scanf-style function, I propose the following behaviour for dict unpacking assignment: items = {'eggs': 2, 'cheese': 3, 'spam': 1} spam, eggs, cheese = **items assert spam == 1 and eggs == 2 and cheese == 3 Syntax ------ target_list [, **target] = **expression `target_list` is a comma-separated list of targets. Targets may be: - simple names, e.g. `spam` and `eggs` - dotted names, e.g. `spam.eggs` - numbered subscripts, e.g. `spam[1]` but is not required to support arbitrary complex targets such as: spam(*args).eggs(2*x + y)[1].cheese # not supported Likewise only int literals are supported for subscripts. (These restrictions may be lifted.) This is similar to the limited range of fields acceptabled by the string format mini-language. The same restrictions apply to `**target`. Each target must be unique. `expression` must evaluate to a dict or other mapping. Assignment proceeds by matching up targets from the left to keys on the right: 1. Every target must be matched exactly by a key. If there is a target without a corresponding key, that is an error. 2. Any key which does not match up to a target is an error, unless a `**target` is given. 3. If `**target` is given, it will collect any excess key:value pairs remaining into a dict. 4. If the targets and keys match up, then the bindings are applied from left to right, binding the target to the value associated with that key. Examples: # Targets are not unique a, b, a = **items => SyntaxError # Too many targets a, b, c = **{'a': 1, 'b': 2} => raises a runtime exception # Too few targets a = **{'a': 1, 'b': 2} => raises a runtime exception a, **extras = **{'a': 1, 'b': 2} assert a == 1 assert extras == {'b': 2} # Equal targets and keys a, b, **extras = **{'a': 1, 'b': 2} assert a == 1 assert b == 2 assert extras == {} # Dotted names from types import SimpleNamespace obj = SimpleNamespace() obj.spam = **{'obj.spam': 1} assert obj.spam == 1 # Subscripts arr = [None]*5 arr[1], arr[3] = **{'arr[3]': 33, 'arr[1]': 11} assert arr == [None, 11, None, 33, None] Assignments to dotted names or subscripts may fail, in which case the assignment may only partially succeed: spam = 'something' eggs = None spam, eggs.attr = {'spam': 1, 'eggs.attr': 2} # raises AttributeError: 'NoneType' object has no attribute 'attr' # but `spam` may have already been bound to 1 (I think that this is undesirable but unavoidable.) Motivating use-cases -------------------- The motivation comes from the discussion for scanf-like functionality. The addition of dict unpacking assignment would allow something like this: pattern = "I'll have {main} and {extra} with {colour} coffee." string = "I'll have spam and eggs with black coffee." main, extra, colour = **scanf(pattern, string) assert main == 'spam' assert extra == 'eggs' assert colour == 'black' But the possibilities are not restricted to string scanning. This will allow functions that return multiple values to choose between returning them by position or by name: height, width = get_dimensions(window) # returns a tuple height, width = **get_dimensions(window) # returns a mapping Developers can choose whichever model best suits their API. Another use-case is dealing with kwargs inside functions and methods: def method(self, **kwargs): spam, eggs, **kw = **kwargs process(spam, eggs) super().method(**kw) -- Steve _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/FJYAE6P5263G3MQMVQ5IUCXUNAUJYQAT/ Code of Conduct: http://python.org/psf/codeofconduct/