Boundaries for unpacking

Python is a lot about iteration, and I often have to get values from an iterable. For this, unpacking is fantastic: a, b, c = iterable One that problem arises is that you don't know when iterable will contain 3 items. In that case, this beautiful code becomes: iterator = iter(iterable) a = next(iterator, "default value") b = next(iterator, "default value") c = next(iterator, "default value") More often than not, I wish there were a way to specify a default value for unpacking. This would also come in handy to get the first or last item of an iterable that can be empty. Indeed: a, *b = iterable *a, b = iterable Would fail if the iterable's iterator cannot yield at least one item. I don't have a clean syntax in mind, so I hope some people will get inspired by it and will make suggestions. I add a couple of ideas: a, b, c with "default value" = iterable a, b, c except "default value" = iterable a, b, c | "default value" = iterable But none of them are great. Another problem is that if you can't use unpacking on generator yielding 1000000 values: a, b = iterable # error a, b, c* = iterable # consume the whole thing, and load it in memory So if I want the 2 first items, I have to go back to: iterator = iter(iterable) a = next(iterator) b = next(iterator) Now, I made a suggestion to allow slicing on any iterable (or at least on any iterator), but I'm not going to hold my breath on it: a, b = iterable[:2] # allow that on generators or a, b = iter(iterable)[:2] # allow that Some have suggested to add a new syntax: a, b = iterable(:2) But even just a way to allow to specify that you just want the 2 first items without unpacking the all iterable would work for me. Ideally though, the 2 options should be able to be used together.

On Thu, Apr 7, 2016 at 1:03 PM, Michel Desmoulin <desmoulinmichel@gmail.com> wrote:
I think rather than adding a new syntax, it would be better to just make one of these work: a, b, c, *d = itertools.chain(iterable, itertools.repeat('default value')) a, b, c = itertools.chain(iterable, itertools.repeat('default value')) a, b, c = itertools.chain.from_iterable(iterable, default='default value')

Ignoring the fact that it's inelegant and verbose (not even counting the import), the biggest problem is that there is no way I will ever remember it. Which mean everytime I want to do it (probably twice a week), I will have to look it up on the doc, just like I do with itertools.groupby or the receipe to iterate on a window of values. Le 07/04/2016 19:44, Todd a écrit :
a, b, c, *d = itertools.chain(iterable, itertools.repeat('default value'))

I actually don't think that's so ugly. Looks fairly clear to me. What about these alternatives?
If you want to stick with builtins:
If your utterable is sliceable and sizeable:
Perhaps add a recipe to itertools, or change the "take" recipe? def unpack(n, iterable, default=None): "Slice the first n items of the iterable, padding with a default" padding = repeat(default) return islice(chain(iterable, padding), n))

Le 07/04/2016 20:15, Michael Selik a écrit :
Well, there is nothing wrong with: a = mylist[0] b = mylist[1] c = mylist[2] But we still prefer unpacking. And this is way more verbose.
They all work, but they are impossible to remember, plus you will need a comment everytime you use them outside of the shell.
Same problem, and as you said, you can forget about generators.
Not a bad idea. Built in would be better, but I can live with itertools. I import it so often I'm wondering if itertools shouldn't be in __builtins__ :)

If I had to make a syntax suggestion, I think it would be a, b, c = iterable or defaults although making or both pseudo None-coalescing and pseudo error-coalescing might be a bit too much sugar. However I think it clearly expresses the idea. That said, I have to ask what the usecase is for dealing with fixed length unpacking where you might not have the fixed length of items. That feels smelly to me. Why are you trying to name values from a variable length list? -Josh On Thu, Apr 7, 2016 at 2:24 PM Michel Desmoulin <desmoulinmichel@gmail.com> wrote:

On 4/7/2016 1:03 PM, Michel Desmoulin wrote:
You need to augment if needed to get at least 3 items. You need to chop if needed to get at most 3 items. The following does exactly this. import itertools as it a, b, c = it.islice(it.chain(iterable, it.repeat(default, 3), 3) You can wrap this if you want with def exactly(iterable, n, default). This might already be a recipe in the itertools doc recipe section. -- Terry Jan Reedy

Michel Desmoulin writes:
It's clean because it's well-defined. Slices on general iterables don't have an OWTDI. For example, "a = somelist[:]" is an idiom for copying somelist to a so that destructive manipulations of a don't change the original. Should "a = someiterable[:]" reproduce those semantics? After "head, tail = someiterable" should tail contain a list or someiterable itself or something else? "WIBNI iterable[] worked" has already been posted to this thread about 5 times, and nobody disagrees that IWBN. But slicing and unpacking of iterables are fraught with such issues. It's time the wishful thinkers got down to edge cases and wrote a PEP.

On Thu, Apr 7, 2016 at 1:03 PM, Michel Desmoulin <desmoulinmichel@gmail.com> wrote:
I think rather than adding a new syntax, it would be better to just make one of these work: a, b, c, *d = itertools.chain(iterable, itertools.repeat('default value')) a, b, c = itertools.chain(iterable, itertools.repeat('default value')) a, b, c = itertools.chain.from_iterable(iterable, default='default value')

Ignoring the fact that it's inelegant and verbose (not even counting the import), the biggest problem is that there is no way I will ever remember it. Which mean everytime I want to do it (probably twice a week), I will have to look it up on the doc, just like I do with itertools.groupby or the receipe to iterate on a window of values. Le 07/04/2016 19:44, Todd a écrit :
a, b, c, *d = itertools.chain(iterable, itertools.repeat('default value'))

I actually don't think that's so ugly. Looks fairly clear to me. What about these alternatives?
If you want to stick with builtins:
If your utterable is sliceable and sizeable:
Perhaps add a recipe to itertools, or change the "take" recipe? def unpack(n, iterable, default=None): "Slice the first n items of the iterable, padding with a default" padding = repeat(default) return islice(chain(iterable, padding), n))

Le 07/04/2016 20:15, Michael Selik a écrit :
Well, there is nothing wrong with: a = mylist[0] b = mylist[1] c = mylist[2] But we still prefer unpacking. And this is way more verbose.
They all work, but they are impossible to remember, plus you will need a comment everytime you use them outside of the shell.
Same problem, and as you said, you can forget about generators.
Not a bad idea. Built in would be better, but I can live with itertools. I import it so often I'm wondering if itertools shouldn't be in __builtins__ :)

If I had to make a syntax suggestion, I think it would be a, b, c = iterable or defaults although making or both pseudo None-coalescing and pseudo error-coalescing might be a bit too much sugar. However I think it clearly expresses the idea. That said, I have to ask what the usecase is for dealing with fixed length unpacking where you might not have the fixed length of items. That feels smelly to me. Why are you trying to name values from a variable length list? -Josh On Thu, Apr 7, 2016 at 2:24 PM Michel Desmoulin <desmoulinmichel@gmail.com> wrote:

On 4/7/2016 1:03 PM, Michel Desmoulin wrote:
You need to augment if needed to get at least 3 items. You need to chop if needed to get at most 3 items. The following does exactly this. import itertools as it a, b, c = it.islice(it.chain(iterable, it.repeat(default, 3), 3) You can wrap this if you want with def exactly(iterable, n, default). This might already be a recipe in the itertools doc recipe section. -- Terry Jan Reedy

Le 07/04/2016 21:18, Terry Reedy a écrit :
Yes and you can also do that for regular slicing on list. But you don't, because you have regular slicing, which is cleaner, and easier to read and remember. This is not only hard to remember, but needs a comment. And an import.

Michel Desmoulin writes:
It's clean because it's well-defined. Slices on general iterables don't have an OWTDI. For example, "a = somelist[:]" is an idiom for copying somelist to a so that destructive manipulations of a don't change the original. Should "a = someiterable[:]" reproduce those semantics? After "head, tail = someiterable" should tail contain a list or someiterable itself or something else? "WIBNI iterable[] worked" has already been posted to this thread about 5 times, and nobody disagrees that IWBN. But slicing and unpacking of iterables are fraught with such issues. It's time the wishful thinkers got down to edge cases and wrote a PEP.
participants (6)
-
Joshua Morton
-
Michael Selik
-
Michel Desmoulin
-
Stephen J. Turnbull
-
Terry Reedy
-
Todd