Oh, that's quite different than mapping patterns in PEP 634. :-(
On Thu, Oct 22, 2020 at 8:28 PM Steven D'Aprano <[email protected]> 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 -- [email protected]
> To unsubscribe send an email to [email protected]
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/[email protected]/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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/UJMLBLEL4W3S4IBG3OO5LH2COCNPS36B/
Code of Conduct: http://python.org/psf/codeofconduct/