Understanding [a] = ["test"] syntax

Please help me understand this syntax. I understand unpacking like this:

>>> a, b = ["test", "test"]
>>> a
'test'
>>> b
'test'

Or like this

>>> a = ["test"]
>>> a
['test']

But this I have never seen before nor do I understand how it works.

>>> [a] = ["test"]
>>> a
'test'

How do the leftside brackets cause it to access ["test"][0]?

Is there a way to split it into multiple lines so I see it more clearly?

Lastly, here’s a double bracket variation which is where I first saw this syntax, please explain this too if it differs from “same as single bracket syntax, but twice”.

>>> [[a]] = [("test",)]
>>> a
'test'

The syntax

>>> a, b = [1, 2]
>>> a
1
>>> b
2

is called “unpacking”. It’s telling the interpreter to assign the individual contents of the iterable to the variables listed. You can use it for a singleton as well:

>>> a = [1]
>>> a
[1]
>>> a, = [1]
>>> a
1

I hope this helps.

The interpreter needs to understand when it is unpacking something, versus just assigning.

a = ["test"] is an assignment: a is now a list with one element.

There are two ways to tell the interpeter “unpack the thing on the right-hand side”. One way is with a comma: a, = ["test"] has a one-element tuple on the left-hand side and so it knows to unpack the list into it. With [a] = ["test"] there’s a list on the left, so again it knows to unpack it.

There’s no difference between square brackets and parentheses here, because the thing on the left-hand side is transient anyway. So you can use (a, b) = ["A", "B"] or [a, b] = ["A", "B"] to mean the same thing. Or you can omit the brackets entirely with a, b = ...

The final example you posted is a case where things are further nested: it’s saying “unpack this thing into a one-element list, and unpack the first element into a one-element list as well”. It’s not common because you would generally try not to get into that situation, but it works.

I wouldn’t really call the left-hand side “transient”. a and b remain defined. There is no list, even briefly. The syntax is part of the assignment statement’s syntax, not an orthogonal combination of a list or tuple display and assignment. Note that set syntax is explicitly forbidden:

>>> {a, b} = [1,2]
  File "<python-input-0>", line 1
    {a, b} = [1,2]
    ^^^^^^
SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='?

At least in CPython, you can see that the style of syntax used doesn’t affect the end result, which is just to unpack the RHS and perform a series of assignments to the targets on the LHS.

>>> import dis
>>> dis.dis("""a, b = [1,2]""")
  0           RESUME                   0

  1           LOAD_CONST               0 (1)
              LOAD_CONST               1 (2)
              BUILD_LIST               2
              UNPACK_SEQUENCE          2
              STORE_NAME               0 (a)
              STORE_NAME               1 (b)
              RETURN_CONST             2 (None)
>>> dis.dis("""[a, b] = [1,2]""")
  0           RESUME                   0

  1           LOAD_CONST               0 (1)
              LOAD_CONST               1 (2)
              BUILD_LIST               2
              UNPACK_SEQUENCE          2
              STORE_NAME               0 (a)
              STORE_NAME               1 (b)
              RETURN_CONST             2 (None)

You can see the different in the AST, though, for what it’s worth:

>>> import ast
>>> print(ast.dump(ast.parse("""a, b = [1,2]"""), indent=2))
Module(
  body=[
    Assign(
      targets=[
        Tuple(
          elts=[
            Name(id='a', ctx=Store()),
            Name(id='b', ctx=Store())],
          ctx=Store())],
      value=List(
        elts=[
          Constant(value=1),
          Constant(value=2)],
        ctx=Load()))])
>>> print(ast.dump(ast.parse("""[a, b] = [1,2]"""), indent=2))
Module(
  body=[
    Assign(
      targets=[
        List(
          elts=[
            Name(id='a', ctx=Store()),
            Name(id='b', ctx=Store())],
          ctx=Store())],
      value=List(
        elts=[
          Constant(value=1),
          Constant(value=2)],
        ctx=Load()))])

A different implementation could, in theory, choose to erase the difference during parsing, or preserve the difference until a and b are defined. But the grammar is pretty specific in distinguishing a start_atom used to define the targets from an expression.

2 Likes

Technically correct! I figured “transient” was reasonable hand-waving for the explanation needed here. :sweat_smile: