> 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

Reply via email to