Why does multiple variable assignment create a list when used with the asterisk operator?

Asked

Viewed 118 times

8

Suppose there is a tuple with three values, ('a', 1, 'b'), and you want to assign the first value in one variable and keep the rest in another. For this you can do:

a, *b = ('a', 1, 'b')

Thus, a will possess the value 'a' while b will possess the value [1, 'b'].

Why the value defined in b is a list?

I cannot see the advantages of creating a changeable structure, like the list, from a fixed value, as I also see no reason to use a structure that semantically should be homogeneous to allocate possibly heterogeneous data.

So much so that when used as argument of a function the created object is a tuple:

def foo(a, *b):
    print(a, b)

foo('a', 1, 'b')  # a (1, 'b')

It wouldn’t make more sense b also be a tuple in a, *b = ('a', 1, 'b')?

2 answers

2


I was reading the book I had quoted in the comments again, and I ended up jumping to the answer. Giving a deep search I ended up finding the discussion:

https://mail.python.org/pipermail/python-3000/2007-May/007312.html

According to Greg on the pep3132 discussion link:

it is not possible to know directly the size of an iterator. The only way to return a tuple would be to first create a list, then create a tuple.

That is, it would take an extra step to process the data. That is why a list is returned.

  • But the question is precisely why "unpack" creates a list. I also didn’t understand the relationship of namedtuple with the question.

  • Because the tuple itself is immutable. As I said " if you do unpacking, it is because you will manipulate the data from the variables you used for unpacking.". The namedtuple is so there is no need to unpack and be able to use the tuple as if it were a variable, there is no need to unpacking. I’ll try to be more concise in my answer.

  • 1

    1) "by default *args assembles a list": the AP proves that when the unpacking is made in the arguments of a function, the return is a tuple and not a list. Your statement makes no sense. 2) "Unpack serves to instantiate an immutable value in a mutable variable": I read the PEP 3132 and I didn’t find that statement. 3) "If you want to work with named tuples, the recommended is to use namedtuple": I didn’t understand the connection with the question and namedtuples is the only native implementation of Named Tuples, so it seems obvious to me the statement

  • "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." namedtuple so there is no need to save in memory a tuple that may already have been created. And there is no need to "find" this statement, it is simple. Tuples are immutable, and have only two senses of unpacking, manipulating data, or naming them. So I quoted namedtuples. The knowledge I’m based on is in a book written by Luciano Ramalho, Fluent Python.

  • 1

    I have Fluent Python, I’ll take a look when I get home. And even with what you mentioned, your statement "Unpack serves to instantiate an immutable value in a mutable variable" still doesn’t make sense. And maybe it wasn’t clear, but the AP would like to understand why they decide for lists instead of tuples and why this behavior isn’t the same when we unpack positional arguments. And just a discussion to understand why this decision was made, we know it’s a list, but it should be a list?

  • Hmm, so, but there it is. What would be the meaning in unpack a tuple for another tuple? That’s what I’m putting on the agenda. If only to name the values of the tuple, wouldn’t it be more interesting to use namedtuple for this case? What would be the sense of creating a tuple from another tuple(?)

  • 1

    a, *b, c = (1, 2, 3, 4, 5) would be 1, (2, 3, 4), 5.. It would make as much sense as a list because you are breaking a sequence into pieces, with the difference that semantically a tuple would make more sense because it is a fixed representation of the "piece" of the sequence. The discussion is legal, the difficult thing is to find an answer as to why this decision was made this way, because in PEP 3132 it is specified that it is a list, but it does not explain why it has to be a list. PS: Which Fluent Python chapter did you refer to? I won’t be able to read it all now until this afternoon hahahah

  • Anything goes to Sopt’s chat in the afternoon... here is too long already

  • page 27 begins Tuple Unpacking. I have to go to work, but I will go there to chat

  • 1

    (did not see how was the previous text of the reply - but the current answer seems satisfactory to me)

Show 5 more comments

0

I don’t know how much this resonates your question, but this implementation (list instead of tuple) allows using * syntax even in cases where the size of the sequence is unknown, as in generators:

>>> gen = (x**2 for x in range(5))
>>> len(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'generator' has no len()
>>> first, *rest = gen
>>> first
0
>>> rest
[1, 4, 9, 16]

Apparently a single implementation is used for the syntax of *, making it work the same way for any iterable - joining all the spare elements in one list - regardless of the type of iterable (list, tuple, generator or even strings):

>>> first, *rest = 'mystring'
>>> first
'm'
>>> rest
['y', 's', 't', 'r', 'i', 'n', 'g']

I imagine it would be possible to implement * syntax in order to return the elements in the same initial data type (tuple for tuple, string for string...), but in some cases (as in the generator) this would not work. And then we would be wondering if it would be better to pass these values to list or tuple... :-)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.