Is there any idea about dictionary destructing?

We know that Python support the destructing of iterable objects. m_iter = (_ for _ in range(10)) a, *b, c = m_iter That's pretty cool! It's really convenient when there're many corner cases to handle with iterable collections. However destructing in Python could be more convenient if we support dictionary destructing. In my opinion, dictionary destructing is not difficult to implement and makes the syntax more expressive. A typical example is data access on nested data structures(just like JSON), destructing a dictionary makes the logic quite clear: data = { "direct": "some data", "nested": { "lst_data": [1, 2, 3], "int_data": 1 } } { "direct": direct, "nested": { "lst_data": [a, b, c], } } = data Dictionary destructing might not be very well-known but it really helps. The operations on nested key-value collections are very frequent, and the codes for business logic are not readable enough until now. Moreover Python is now popular in data processing which must be enhanced by the entire support of data destructing. Here are some implementations of other languages: Elixir, which is also a popular dynamic language nowadays. iex> %{} = %{:a => 1, 2 => :b} %{2 => :b, :a => 1} iex> %{:a => a} = %{:a => 1, 2 => :b} %{2 => :b, :a => 1} iex> a 1 iex> %{:c => c} = %{:a => 1, 2 => :b} ** (MatchError) no match of right hand side value: %{2 => :b, :a => 1} And in F#, there is something similar to dictionary destructing(actually, this destructs `struct` instead) type MyRecord = { Name: string; ID: int } let IsMatchByName record1 (name: string) = match record1 with | { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true | _ -> false let recordX = { Name = "Parker"; ID = 10 } let isMatched1 = IsMatchByName recordX "Parker" let isMatched2 = IsMatchByName recordX "Hartono" All of them partially destructs(or matches) a dictionary. thautwarm

This would be a very handy feature, but Coconut (which is just python with some extra functional-style features) also has support for this kind of pattern-matching: http://coconut-lang.org Since Coconut will compile to Python (2 or 3) you can just write in Coconut and use the resulting code in your Python. Using your first example in coconut would be nearly identical, except I believe the entire dictionary must be specified (I am not sure about this). data = { 'direct': 'some data', 'nested': { 'lst_data': [1, 2, 3], 'int_data': 1 } } { 'direct': direct, 'nested': { 'lst_data': [a, b, c], 'int_data': _ } } = data print(direct) print(a) print(b) print(c) And this should print: On Sat, Apr 7, 2018 at 1:26 PM, thautwarm <yaoxiansamma@gmail.com> wrote:

There was a long thread last year on a subject, titled "Dictionary destructing and unpacking.": https://mail.python.org/pipermail/python-ideas/2017-June/045963.html You might want to read through it and see what ideas and problems were raised then. In that discussion, there's also a link to an older pattern matching thread: https://mail.python.org/pipermail/python-ideas/2015-April/032907.html Eric On 4/7/2018 1:26 PM, thautwarm wrote:

I have an idea for an inovative, unanbiguous, straightforward and backwards compatible syntax for that, that evena llows one to pass metadata along the operation so that the results can be tweaked acording to each case's needs. What about: new_data = dict_feed({ "direct": "some data", "nested": { "lst_data": [1, 2, 3], "int_data": 1 } }, data ) we could even call this approach a name such as "function call". In other words, why to bloat the language with hard to learn, error prone, grit-looking syntax, when a simple plain function call is perfectly good, all you need to do over your suggestion is to type the function name and a pair of parentheses? On 7 April 2018 at 14:26, thautwarm <yaoxiansamma@gmail.com> wrote:

On 9 April 2018 at 22:10, Brett Cannon <brett@python.org> wrote:
On Mon, 9 Apr 2018 at 05:18 Joao S. O. Bueno <jsbueno@python.org.br> wrote:
we could even call this approach a name such as "function call".
The harsh sarcasm is not really called for.
Indeed - on rereading, I have to agree on that. I do apologize for the sarcasm. - really, I not only stand corrected: I recognize i was incorrect to start with. But my argument that this feature is needless language bloat stands. On the othe hand, as for getting variable names out of _shallow_ mappings, I've built that feature in a package I authored, using a context manager to abuse the import mechanism - In [96]: from extradict import MapGetter In [97]: data = {"A": None, "B": 10} In [98]: with MapGetter(data): ...: from data import A, B ...: In [99]: A, B Out[99]: (None, 10) That is on Pypi and can be used by anyone right now.

Your library seems difficult to extract values from nested dictionary, and when the key is not an identifier it's also embarrassed. For sure we can have a library using graphql syntax to extract data from the dict of any schema, but that's not my point. I'm focused on the consistency of the language itself. {key: value_pattern, **_} = {key: value, **_} The reason why it's important is that, when destructing/constructing for built-in data structures are not supported completely, people might ask why "[a, *b] = c" is ok but "{"a": a, **b} = c" not. If only multiple assignment is supported, why "(a, (b, c)) = d" could be ok? It's exactly destructing! And then if people think destructing of data structures is ok, they might be curious about what's the boundary of this feature. If you just tell them, "oh, it's just special, it just works for iterables, even you can partially destruct the dict in argument passing, you cannot use it in a simple statement!" >> def f(a, b, **c): print (a, b, c) >> f(a=1, **{'b': 2}) 1 2 {} >> {'a': a, 'b': b, **c} = {'a': 1, **{'b': 2}} SyntaxError: can't assign to literal Above example could be confusing in some degree, I think. If we don't have so many convenient helpers for function call, in terms of consistency, it might be even better... 2018-04-10 9:23 GMT+08:00 Joao S. O. Bueno <jsbueno@python.org.br>:

On Tue, Apr 10, 2018 at 03:29:08PM +0800, Thautwarm Zhao wrote:
I'm focused on the consistency of the language itself.
Consistency is good, but it is not the only factor to consider. We must guard against *foolish* consistency: adding features just for the sake of matching some other, often barely related, feature. Each feature must justify itself, and consistency with something else is merely one possible attempt at justification.
{key: value_pattern, **_} = {key: value, **_}
If I saw that, I would have no idea what it could even possibly do. Let's pick the simplest concrete example I can think of: {'A': 1, **{}} = {'A': 0, **{}} I cannot interpret what that should do. Is it some sort of pattern-matching? An update? What is the result? It is obviously some sort of binding operation, an assignment, but an assignment to what? Sequence binding and unpacking was obvious the first time I saw it. I had no problem guessing what: a, b, c = 1, 2, 3 meant, and once I had seen that, it wasn't hard to guess what a, b, c = *sequence meant. From there it is easy to predict extended unpacking. But I can't say the same for this. I can almost see the point of: a, b, c, = **{'a': 1, 'b': 2, 'c': 3} but I'm having trouble thinking of a situation where I would actually use it. But your syntax above just confuses me.
People can ask all sorts of questions. I've seen people ask why Python doesn't support line numbers and GOTO. We're allowed to answer "Because it is a bad idea", or even "Because we don't think it is good enough to justify the cost".
If only multiple assignment is supported, why "(a, (b, c)) = d" could be ok? It's exactly destructing!
That syntax is supported. I don't understand your point here.
I have no idea what you expect it to do. Even something simpler: {'a': a} = {'a': 2} leaves me in the dark. -- Steve

I must say I can't really see the point either. If you say like:
{'a': a, 'b': b, **c} = {'a': 1, **{'b': 2}}
Do you basically mean: c = {'a': 1, **{'b': 2}} a = c.pop("a") b = c.pop("b") # ? That's the only thing I could think of. I think most of these problems could be solved with pop and the occasional list comprehension like this: a, b, c = [{'a':1,'b':2,'c':3}.pop(key) for key in ('a', 'b', 'c')] or for your example: c = {'a': 1, **{'b': 2}} # I suppose this one would generally # be dynamic, but I need a name here. a, b = [c.pop(key) for key in ('a', 'b')] would extract all the keys you need, and has the advantage that you don't need hardcoded dict structure if you expand it to nested dicts. It's even less writing, and just as extensible to nested dicts. And if you dont actually want to destruct (tuples and lists aren't destroyed either), just use __getitem__ access instead of pop. 2018-04-10 11:21 GMT+02:00 Steven D'Aprano <steve@pearwood.info>:

Here's one argument why sequence unpacking is more important than dict unpacking. Without sequence unpacking, you have a long sequence, to get at a specific item you'd need to use indexing, where you often end up having to remember the indices for each type of information. Say you have points of the form (x, y, z, t), to get at the t coordinate you'd have to write p[3]. With sequence unpacking you can write x, y, z, t = p and then you can use the individual variables in the subsequent code. However if your point had the form {'x': x, 'y': y, 'z': z, 't': t}, you could just write p['t']. This is much more mnemonic than p[3]. All the rest follows -- after a while extended forms of iterable unpacking start making sense. But for dicts the use case is just much less common. (If you're doing a lot of JSON you might have a different view on that. You should probably use some kind of schema-guided parser though.) -- --Guido van Rossum (python.org/~guido)

This would be a very handy feature, but Coconut (which is just python with some extra functional-style features) also has support for this kind of pattern-matching: http://coconut-lang.org Since Coconut will compile to Python (2 or 3) you can just write in Coconut and use the resulting code in your Python. Using your first example in coconut would be nearly identical, except I believe the entire dictionary must be specified (I am not sure about this). data = { 'direct': 'some data', 'nested': { 'lst_data': [1, 2, 3], 'int_data': 1 } } { 'direct': direct, 'nested': { 'lst_data': [a, b, c], 'int_data': _ } } = data print(direct) print(a) print(b) print(c) And this should print: On Sat, Apr 7, 2018 at 1:26 PM, thautwarm <yaoxiansamma@gmail.com> wrote:

There was a long thread last year on a subject, titled "Dictionary destructing and unpacking.": https://mail.python.org/pipermail/python-ideas/2017-June/045963.html You might want to read through it and see what ideas and problems were raised then. In that discussion, there's also a link to an older pattern matching thread: https://mail.python.org/pipermail/python-ideas/2015-April/032907.html Eric On 4/7/2018 1:26 PM, thautwarm wrote:

I have an idea for an inovative, unanbiguous, straightforward and backwards compatible syntax for that, that evena llows one to pass metadata along the operation so that the results can be tweaked acording to each case's needs. What about: new_data = dict_feed({ "direct": "some data", "nested": { "lst_data": [1, 2, 3], "int_data": 1 } }, data ) we could even call this approach a name such as "function call". In other words, why to bloat the language with hard to learn, error prone, grit-looking syntax, when a simple plain function call is perfectly good, all you need to do over your suggestion is to type the function name and a pair of parentheses? On 7 April 2018 at 14:26, thautwarm <yaoxiansamma@gmail.com> wrote:

On 9 April 2018 at 22:10, Brett Cannon <brett@python.org> wrote:
On Mon, 9 Apr 2018 at 05:18 Joao S. O. Bueno <jsbueno@python.org.br> wrote:
we could even call this approach a name such as "function call".
The harsh sarcasm is not really called for.
Indeed - on rereading, I have to agree on that. I do apologize for the sarcasm. - really, I not only stand corrected: I recognize i was incorrect to start with. But my argument that this feature is needless language bloat stands. On the othe hand, as for getting variable names out of _shallow_ mappings, I've built that feature in a package I authored, using a context manager to abuse the import mechanism - In [96]: from extradict import MapGetter In [97]: data = {"A": None, "B": 10} In [98]: with MapGetter(data): ...: from data import A, B ...: In [99]: A, B Out[99]: (None, 10) That is on Pypi and can be used by anyone right now.

Your library seems difficult to extract values from nested dictionary, and when the key is not an identifier it's also embarrassed. For sure we can have a library using graphql syntax to extract data from the dict of any schema, but that's not my point. I'm focused on the consistency of the language itself. {key: value_pattern, **_} = {key: value, **_} The reason why it's important is that, when destructing/constructing for built-in data structures are not supported completely, people might ask why "[a, *b] = c" is ok but "{"a": a, **b} = c" not. If only multiple assignment is supported, why "(a, (b, c)) = d" could be ok? It's exactly destructing! And then if people think destructing of data structures is ok, they might be curious about what's the boundary of this feature. If you just tell them, "oh, it's just special, it just works for iterables, even you can partially destruct the dict in argument passing, you cannot use it in a simple statement!" >> def f(a, b, **c): print (a, b, c) >> f(a=1, **{'b': 2}) 1 2 {} >> {'a': a, 'b': b, **c} = {'a': 1, **{'b': 2}} SyntaxError: can't assign to literal Above example could be confusing in some degree, I think. If we don't have so many convenient helpers for function call, in terms of consistency, it might be even better... 2018-04-10 9:23 GMT+08:00 Joao S. O. Bueno <jsbueno@python.org.br>:

On Tue, Apr 10, 2018 at 03:29:08PM +0800, Thautwarm Zhao wrote:
I'm focused on the consistency of the language itself.
Consistency is good, but it is not the only factor to consider. We must guard against *foolish* consistency: adding features just for the sake of matching some other, often barely related, feature. Each feature must justify itself, and consistency with something else is merely one possible attempt at justification.
{key: value_pattern, **_} = {key: value, **_}
If I saw that, I would have no idea what it could even possibly do. Let's pick the simplest concrete example I can think of: {'A': 1, **{}} = {'A': 0, **{}} I cannot interpret what that should do. Is it some sort of pattern-matching? An update? What is the result? It is obviously some sort of binding operation, an assignment, but an assignment to what? Sequence binding and unpacking was obvious the first time I saw it. I had no problem guessing what: a, b, c = 1, 2, 3 meant, and once I had seen that, it wasn't hard to guess what a, b, c = *sequence meant. From there it is easy to predict extended unpacking. But I can't say the same for this. I can almost see the point of: a, b, c, = **{'a': 1, 'b': 2, 'c': 3} but I'm having trouble thinking of a situation where I would actually use it. But your syntax above just confuses me.
People can ask all sorts of questions. I've seen people ask why Python doesn't support line numbers and GOTO. We're allowed to answer "Because it is a bad idea", or even "Because we don't think it is good enough to justify the cost".
If only multiple assignment is supported, why "(a, (b, c)) = d" could be ok? It's exactly destructing!
That syntax is supported. I don't understand your point here.
I have no idea what you expect it to do. Even something simpler: {'a': a} = {'a': 2} leaves me in the dark. -- Steve

I must say I can't really see the point either. If you say like:
{'a': a, 'b': b, **c} = {'a': 1, **{'b': 2}}
Do you basically mean: c = {'a': 1, **{'b': 2}} a = c.pop("a") b = c.pop("b") # ? That's the only thing I could think of. I think most of these problems could be solved with pop and the occasional list comprehension like this: a, b, c = [{'a':1,'b':2,'c':3}.pop(key) for key in ('a', 'b', 'c')] or for your example: c = {'a': 1, **{'b': 2}} # I suppose this one would generally # be dynamic, but I need a name here. a, b = [c.pop(key) for key in ('a', 'b')] would extract all the keys you need, and has the advantage that you don't need hardcoded dict structure if you expand it to nested dicts. It's even less writing, and just as extensible to nested dicts. And if you dont actually want to destruct (tuples and lists aren't destroyed either), just use __getitem__ access instead of pop. 2018-04-10 11:21 GMT+02:00 Steven D'Aprano <steve@pearwood.info>:

Here's one argument why sequence unpacking is more important than dict unpacking. Without sequence unpacking, you have a long sequence, to get at a specific item you'd need to use indexing, where you often end up having to remember the indices for each type of information. Say you have points of the form (x, y, z, t), to get at the t coordinate you'd have to write p[3]. With sequence unpacking you can write x, y, z, t = p and then you can use the individual variables in the subsequent code. However if your point had the form {'x': x, 'y': y, 'z': z, 't': t}, you could just write p['t']. This is much more mnemonic than p[3]. All the rest follows -- after a while extended forms of iterable unpacking start making sense. But for dicts the use case is just much less common. (If you're doing a lot of JSON you might have a different view on that. You should probably use some kind of schema-guided parser though.) -- --Guido van Rossum (python.org/~guido)
participants (9)
-
Brett Cannon
-
Eric V. Smith
-
Guido van Rossum
-
Jacco van Dorp
-
Joao S. O. Bueno
-
Nikolas Vanderhoof
-
Steven D'Aprano
-
thautwarm
-
Thautwarm Zhao