> On Feb 9, 2015, at 8:34 PM, Neil Girdhar <mistersh...@gmail.com> wrote: > > > > On Mon, Feb 9, 2015 at 7:53 PM, Donald Stufft <don...@stufft.io > <mailto:don...@stufft.io>> wrote: > >> On Feb 9, 2015, at 7:29 PM, Neil Girdhar <mistersh...@gmail.com >> <mailto:mistersh...@gmail.com>> wrote: >> >> For some reason I can't seem to reply using Google groups, which is is >> telling "this is a read-only mirror" (anyone know why?) Anyway, I'm going >> to answer as best I can the concerns. >> >> Antoine said: >> >> To be clear, the PEP will probably be useful for one single line of >> Python code every 10000. This is a very weak case for such an intrusive >> syntax addition. I would support the PEP if it only added the simple >> cases of tuple unpacking, left alone function call conventions, and >> didn't introduce **-unpacking. >> >> To me this is more of a syntax simplification than a syntax addition. For >> me the **-unpacking is the most useful part. Regarding utility, it seems >> that a many of the people on r/python were pretty excited about this PEP: >> http://www.reddit.com/r/Python/comments/2synry/so_8_peps_are_currently_being_proposed_for_python/ >> >> <http://www.reddit.com/r/Python/comments/2synry/so_8_peps_are_currently_being_proposed_for_python/> >> >> — >> >> Victor noticed that there's a mistake with the code: >> >> >>> ranges = [range(i) for i in range(5)] >> >>> [*item for item in ranges] >> [0, 0, 1, 0, 1, 2, 0, 1, 2, 3] >> >> It should be a range(4) in the code. The "*" applies to only item. It is >> the same as writing: >> >> [*range(0), *range(1), *range(2), *range(3), *range(4)] >> >> which is the same as unpacking all of those ranges into a list. >> >> > function(**kw_arguments, **more_arguments) >> If the key "key1" is in both dictionaries, more_arguments wins, right? >> >> There was some debate and it was decided that duplicate keyword arguments >> would remain an error (for now at least). If you want to merge the >> dictionaries with overriding, then you can still do: >> >> function(**{**kw_arguments, **more_arguments}) >> >> because **-unpacking in dicts overrides as you guessed. >> >> — >> >> >> >> On Mon, Feb 9, 2015 at 7:12 PM, Donald Stufft <don...@stufft.io >> <mailto:don...@stufft.io>> wrote: >> >>> On Feb 9, 2015, at 4:06 PM, Neil Girdhar <mistersh...@gmail.com >>> <mailto:mistersh...@gmail.com>> wrote: >>> >>> Hello all, >>> >>> The updated PEP 448 (https://www.python.org/dev/peps/pep-0448/ >>> <https://www.python.org/dev/peps/pep-0448/>) is implemented now based on >>> some early work by Thomas Wouters (in 2008) and Florian Hahn (2013) and >>> recently completed by Joshua Landau and me. >>> >>> The issue tracker http://bugs.python.org/issue2292 >>> <http://bugs.python.org/issue2292> has a working patch. Would someone be >>> able to review it? >>> >> >> I just skimmed over the PEP and it seems like it’s trying to solve a few >> different things: >> >> * Making it easy to combine multiple lists and additional positional args >> into a function call >> * Making it easy to combine multiple dicts and additional keyword args into >> a functional call >> * Making it easy to do a single level of nested iterable "flatten". >> >> I would say it's: >> * making it easy to unpack iterables and mappings in function calls >> * making it easy to unpack iterables into list and set displays and >> comprehensions, and >> * making it easy to unpack mappings into dict displays and comprehensions. >> >> >> >> Looking at the syntax in the PEP I had a hard time detangling what exactly >> it was doing even with reading the PEP itself. I wonder if there isn’t a way >> to combine simpler more generic things to get the same outcome. >> >> Looking at the "Making it easy to combine multiple lists and additional >> positional args into a function call" aspect of this, why is: >> >> print(*[1], *[2], 3) better than print(*[1] + [2] + [3])? >> >> That's already doable in Python right now and doesn't require anything new >> to handle it. >> >> Admittedly, this wasn't a great example. But, if [1] and [2] had been >> iterables, you would have to cast each to list, e.g., >> >> accumulator = [] >> accumulator.extend(a) >> accumulator.append(b) >> accumulator.extend(c) >> print(*accumulator) >> >> replaces >> >> print(*a, b, *c) >> >> where a and c are iterable. The latter version is also more efficient >> because it unpacks only a onto the stack allocating no auxilliary list. > > Honestly that doesn’t seem like the way I’d write it at all, if they might > not be lists I’d just cast them to lists: > > print(*list(a) + [b] + list(c)) > > Sure, that works too as long as you put in the missing parentheses.
There are no missing parentheses, the * and ** is last in the order of operations (though the parens would likely make that more clear). > > > But if casting to list really is that big a deal, then perhaps a better > solution is to simply make it so that something like ``a_list + an_iterable`` > is valid and the iterable would just be consumed and +’d onto the list. That > still feels like a more general solution and a far less surprising and easier > to read one. > > I understand. However I just want to point out that 448 is more general. > There is no binary operator for generators. How do you write (*a, *b, *c)? > You need to use itertools.chain(a, b, c). I don’t feel like using itertools.chain is a bad thing TBH, it’s extremely clear as to what’s going on, you’re chaining a bunch a bunch of iterables together. I would not however be super upset if the ability to do * and ** multiple times in a function was added, I just don’t think it’s very useful for * (since you can already get that behavior with things I believe are clear-er) and I think getting similar constructs for ** would bring that up to parity. I am really really -1 on the comprehension syntax. > > > >> >> >> Looking at the "making it easy to do a single level of nsted iterable >> 'flatten'"" aspect of this, the example of: >> >> >>> ranges = [range(i) for i in range(5)] >> >>> [*item for item in ranges] >> [0, 0, 1, 0, 1, 2, 0, 1, 2, 3] >> >> Conceptually a list comprehension like [thing for item in iterable] can be >> mapped to a for loop like this: >> >> result = [] >> for item in iterable: >> result.append(thing) >> >> However [*item for item in ranges] is mapped more to something like this: >> >> result = [] >> for item in iterable: >> result.extend(*item) >> >> I feel like switching list comprehensions from append to extend just because >> of a * is really confusing and it acts differently than if you just did >> *item outside of a list comprehension. I feel like the itertools.chain() way >> of doing this is *much* clearer. >> >> Finally there's the "make it easy to combine multiple dicts into a function >> call" aspect of this. This I think is the biggest thing that this PEP >> actually adds, however I think it goes around it the wrong way. Sadly there >> is nothing like [1] + [2] for dictionaries. The closest thing is: >> >> kwargs = dict1.copy() >> kwargs.update(dict2) >> func(**kwargs) >> >> So what I wonder is if this PEP wouldn't be better off just using the >> existing methods for doing the kinds of things that I pointed out above, and >> instead defining + or | or some other symbol for something similar to [1] + >> [2] but for dictionaries. This would mean that you could simply do: >> >> func(**dict1 | dict(y=1) | dict2) >> >> instead of >> >> dict(**{'x': 1}, y=2, **{'z': 3}) >> >> I feel like not only does this genericize way better but it limits the >> impact and new syntax being added to Python and is a ton more readable. > > > Honestly the use of * and ** in functions doesn’t bother me a whole lot, > though i don’t see much use for it over what’s already available for lists > (and I think doing something similarly generic for mapping is a better idea). > What really bothers me is these parts: > > * making it easy to unpack iterables into list and set displays and > comprehensions, and > * making it easy to unpack mappings into dict displays and comprehensions. > > I feel like these are super wrong and if they were put in I’d probably end up > writing a linter to disallow them in my own code bases. > > I feel like adding a special case for * in list comprehensions breaks the > “manually expanded” version of those. Switching from append to extend inside > of a list comprehension because of a * doesn’t make any sense to me. I can’t > seem to construct any for loop that mimics what this PEP proposes as [*item > for item in iterable] without fundamentally changing the operation that > happens in each loop of the list comprehension. > > > I don't know what you mean by this. You can write [*item for item in > iterable] in current Python as [it for item in iterable for it in item]. You > can unroll that as: > a = [] > for item in iterable: > for it in item: > a.append(it) > > — or yield for generators or add for sets. I don’t think * means “loop” anywhere else in Python and I would never “guess” that [*item for item in iterable] meant that. It’s completely non intuitive. Anywhere else you see *foo it’s unpacking a tuple not making an inner loop. That means that anywhere else in Python *item is the same thing as item[0], item[1], item[2], …, but this PEP makes it so just inside of a comprehension it actually means “make a second, inner loop” instead of what I think anyone who has learned that syntax would expect, which is it should be equivalent to [(item[0], item[1], item[2], …) for item in iterable]. --- Donald Stufft PGP: 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com