Oh, that's quite different than mapping patterns in PEP 634. :-( On Thu, Oct 22, 2020 at 8:28 PM Steven D'Aprano <st...@pearwood.info> wrote:
> 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/ > -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
_______________________________________________ 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/UJMLBLEL4W3S4IBG3OO5LH2COCNPS36B/ Code of Conduct: http://python.org/psf/codeofconduct/