On Wed, Aug 12, 2015 at 11:44:05AM -0700, Scott Sanderson wrote:
I think the sequence unpacking version above reads much better than this hypothetical dict unpacking version: {'foo': foo, 'bar': bar, 'baz': baz, 'spam': spam, 'eggs': eggs, 'cheese': cheese} = json.loads(json_dict)
As with many things in Python, I think that how you format this expression makes a big difference. I'd write it like this:
{ 'foo': foo, 'bar': bar, 'baz': baz, 'spam': spam, 'eggs': eggs, 'cheese': cheese, } = json.loads(json_dict)
That's still awfully verbose, and not much of a saving from: foo = d['foo'] bar = d['bar'] baz = d['baz'] etc. You save a little bit of typing, but not that much.
I prefer this to your example of unpacking from a list comprehension because I think it does a better job of expressing to a reader the expected structure of the input data.
I don't think it does. I think the above would be incomprehensible to somebody who hasn't learned the details of this. It looks like you are creating a dict, but not assigning the dict to anything. And where do the unquoted values foo, bar, etc. come from? They look like they should come from already existing local variables: {'foo': foo} as an expression (rather than an assignment target) requires an existing foo variable (otherwise you get a NameError). So the behaviour has to be learned, it isn't something that the reader can extrapolate from other assignment syntax. It isn't obvious what this does: {foo: bar} = some_dict because there's no one-to-one correspondence between assignment target and assignment name. With sequence unpacking, the targets are obvious: foo, bar, baz = ... clearly has assignment targets foo, bar and baz. What else could they be? It's easy to extrapolate it from single assignment foo = ... But with your syntax, you have keys and values, and it isn't clear what gets used for what. The dict display form doesn't look like any other assignment target, you have to learn it as a special case. A reader who hasn't learned the rules could be forgiven for guessing any of the following rules: (1) create a variable foo from existing variable bar (2) create a variable foo from some_dict['bar'] (3) create a variable with the name given by the value of foo, from some_dict['bar'] (4) create a variable bar from some_dict['foo'] (5) create a variable with the name given by the value of bar, from some_dict['foo'] and others. You could make that a bit more clear by requiring the keys to be quoted, so {foo: bar} = ... would be illegal, and you have to write {'foo': 'bar'}, but that's annoying. Or we could go the other way and not quote anything: {foo: bar} = d could create variable foo from d['bar']. That's not bad looking, and avoids all the quote marks, but I don't think people would guess that's the behaviour. It still doesn't look like an assignment target. And the common case is still verbose: {foo: foo} = ... What if we have expressions in there? {foo.upper() + 's': bar} = some_dict {foo: bar or baz} = some_dict I would hope both of those are syntax errors! But maybe somebody will want them. At least, some people will expect them, because that sort of thing works in dict displays. You even hint at arbitrary values below, with a tuple (baz_x, baz_y).
It's also much easier to modify this to extract nested values, ala:
{ 'foo': foo, 'bar': bar, 'baz': (baz_x, baz_y), 'spam': spam, 'eggs': eggs, 'cheese': cheese, } = json.loads(json_dict)
So baz is a tuple of d['baz_x'], d['baz_y']? Does this mean you want to allow arbitrary expressions for the values? {'foo': func(foo or bar.upper() + "s") + baz} = d If so, what are the scoping rules? Which of func, foo, bar and baz are looked up from the right-hand side dict, and which are taken from the current scope? I think allowing arbitrary expressions cannot work in any reasonable manner, but special casing tuples (baz_x, baz_y) is too much of a special case.
More practical, and in the spirit of tuple unpacking:
spam, eggs, cheese = **expression
I like how concise this syntax is. I'd be sad that it doesn't allow unpacking of nested expressions, though I think we disagree on whether that's actually an issue. A more substantial objection might be that this could only work on mapping objects with strings for keys.
Does that mean that you expect your syntax to support non-identifier key lookups? {'foo': 123, 'bar': 'x y', 'baz': None} = d will look for keys 123 (or should that be '123'?), 'x y' and None (or possibly 'None')? If so, I think you've over-generalised from a fairly straightforward use-case: unpack key:values in a mapping to variables with the same name as the keys and YAGNI applies. For cases where the keys are not the same as the variables, or you want to use non-identifier keys, just use the good-old fashioned form: variable = d['some non-identifier'] -- Steve