Why does unpacking give a list instead of a tuple in Python?
Asked Answered
S

1

9

This is really strange to me, because by default I thought unpacking gives tuples.

In my case I want to use the prefix keys for caching, so a tuple is preferred.

# The r.h.s is a tuple, equivalent to (True, True, 100)
*prefix, seed = ml_logger.get_parameters("Args.attn", "Args.memory_gate", "Args.seed")
assert type(prefix) is list

But I thought unpacking would return a tuple instead.

Here is the relevant PEP: https://www.python.org/dev/peps/pep-3132/

-- Update --

Given the comment and answers bellow, specifically I was expecting the unpacking to give a tuple because in function arguments a spread arg is always a tuple instead of a list.

As Jason pointed out, during unpacking one would not be able to know the length of the result ahead of time, so implementation-wise the catch-all has to start as a list for dynamic appends. Converting it to a list is a waste of effort the majority of the time.

Semantically, I would prefer to have a tuple for consistency.

Sapir answered 25/4, 2020 at 16:22 Comment(3)
In that PEP that you link to: "This PEP proposes a change to iterable unpacking syntax, allowing to specify a "catch-all" name which will be assigned a list of all items not assigned to a "regular" name."Incardination
The catch-all pretty much has to be a list: there's no general way to predict how big it's going to be (the RHS might be something like a generator, for example), but a tuple has to have an exact size specified to be created at all. So collecting items into a list is the only practical choice. (The list could have been implicitly converted to a tuple afterwards - which would be good in your case, but not so good in cases where a list is actually needed, and is completely wasted effort in the majority of cases where the type doesn't matter.)Disingenuous
@Disingenuous Thanks for your comment! That helps with clarifying the reasoning behind the current pattern. IMO this seems to be a leak from the implementation/efficiency side that is one level down. On a semantic level those are not concerns.Sapir
S
7

This issue was mentioned in that PEP (PEP 3132):

After a short discussion on the python-3000 list [1], the PEP was accepted by Guido in its current form. Possible changes discussed were: [...]

  • Try to give the starred target the same type as the source iterable, for example, b in a, *b = 'hello' would be assigned the string 'ello'. This may seem nice, but is impossible to get right consistently with all iterables.

  • Make the starred target a tuple instead of a list. This would be consistent with a function's *args, but make further processing of the result harder.

But as you can see, these features currently are not implemented:

In [1]: a, *b, c = 'Hello!'
In [2]: print(a, b, c)
H ['e', 'l', 'l', 'o'] !

Maybe, mutable lists are more appropriate for this type of unpacking.

Slumber answered 25/4, 2020 at 16:46 Comment(1)
Doing bulleted point 1 seems hard. My surprise comes from the fast that I expected bulleted point 2 to be the convention, in that when I use spread args I always get tuples instead of other types of iterables. So it got burnt into my cortex.Sapir

© 2022 - 2024 — McMap. All rights reserved.