3.8. Sequence Unpack

3.8.1. Rationale

a = 1
a, b = 1, 2
a, b, c = 1, 2, 3

a, b, c = (1, 2, 3)
a, b, c = [1, 2, 3]
a, b, c = {1, 2, 3}
(a, b, c) = (1, 2, 3)
(a, b, c) = [1, 2, 3]

[a, b, c] = [1, 2, 3]
[a, b, c] = (1, 2, 3)

3.8.2. Errors

Listing 3.33. Note, that set is unordered collection
{a, b, c} = {1, 2, 3}
# SyntaxError: can't assign to literal
Listing 3.34. Too many values to unpack
a, b, c = [1, 2, 3, 4]
# ValueError: too many values to unpack (expected 3)
Listing 3.35. Not enough values to unpack
a, b, c, d = [1, 2, 3]
# ValueError: not enough values to unpack (expected 4, got 3)

3.8.3. Arbitrary Number of Arguments

Listing 3.36. Unpacking values at the right side
a, b, *c = [1, 2, 3, 4]

a               # 1
b               # 2
c               # [3, 4]
Listing 3.37. Unpacking values at the left side
*a, b, c = [1, 2, 3, 4]

a               # [1, 2]
b               # 3
c               # 4
Listing 3.38. Unpacking values from both sides at once
a, *b, c = [1, 2, 3, 4]

a               # 1
b               # [2, 3]
c               # 4
Listing 3.39. Cannot unpack from both sides at once
*a, b, *c = [1, 2, 3, 4]
# SyntaxError: two starred expressions in assignment
Listing 3.40. Unpacking from variable length
a, *b, c = [1, 2]

a               # 1
b               # []
c               # 2
Listing 3.41. Unpacking requires values for required arguments
a, *b, c = [1]
# ValueError: not enough values to unpack (expected at least 2, got 1)

3.8.4. Nested

a, (b, c) = [1, (2, 3)]

a               # 1
b               # 2
c               # 3

3.8.5. Convention

first, *middle, last = [1, 2, 3, 4]

first           # 1
middle          # [2, 3]
last            # 4
first, second, *others = [1, 2, 3, 4]

first               # 1
second              # 2
others              # [3, 4]

3.8.6. Omitting Values

  • _ is regular variable name, not a special Python syntax

  • _ by convention is used for data we don't want to access in future

line = 'Jan,Twardowski'

a, _ = line.split(',')
a, _, _ = 1, 2, 3

print(a)            # 1
_, interesting, _ = 1, 2, 3

print(interesting)  # 2
line = '4.9,3.1,1.5,0.1,setosa'

*_, label = line.split(',')

label               # setosa
line = 'twardowski:x:1001:1001:Jan Twardowski:/home/twardowski:/bin/bash'

username, _, _, _, full_name, *_ = line.split(':')

username            # twardowski
full_name           # Jan Twardowski
line = 'twardowski:x:1001:1001:Jan Twardowski:/home/twardowski:/bin/bash'

username, *_, home, _ = line.split(':')

username            # twardowski
home                # /home/twardowski
_, (interesting, _) = [1, (2, 3)]

interesting         # 2

3.8.7. Using in a Loop

*features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')

features            # [5.8, 2.7, 5.1, 1.9]
label               # 'virginica'
DATA = [
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
]

for *features, label in DATA:
    avg = sum(features) / len(features)
    print(label, avg)

# virginica 3.875
# setosa 2.55
# versicolor 3.475
DATA = [
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
]

for *_, label in DATA:
    print(label)

# virginica
# setosa
# versicolor

3.8.8. Examples

*features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')

features        # [5.8, 2.7, 5.1, 1.9]
label           # 'virginica'
line = '5.4,3.9,1.3,0.4,setosa'

*features, label = line.split(',')
avg = sum(features) / len(features)

label           # 'setosa'
avg             # 2.75
line = 'ares3,watney,lewis,vogel,johanssen'

mission, *crew = line.split(',')

mission         # ares3
crew            # ['watney', 'lewis', 'vogel', 'johanssen']
def parse(line):
    mission, *crew = line.split(',')
    crew = ' and '.join(name.title() for name in crew)
    print(mission.upper(), crew)


parse('ares3,watney,lewis,vogel,johanssen')
# ARES3 Watney and Lewis and Vogel and Johanssen

parse('apollo18,twardowski,ivanovic')
# APOLLO18 Twardowski and Ivanovic
first, second, *others = range(10)

first           # 0
second          # 1
others          # [2, 3, 4, 5, 6, 7, 8, 9]

3.8.9. Assignments

3.8.9.1. Unpacking from sequence

English
  1. Use data from "Input" section (see below)

  2. Using str.split() split input data by white space

  3. Separate ip address and host names

  4. Use asterisk * notation

  5. Compare result with "Output" section (see below)

Polish
  1. Użyj danych z sekcji "Input" (patrz poniżej)

  2. Używając str.split() podziel dane wejściowe po białych znakach

  3. Odseparuj adres ip i nazw hostów

  4. Skorzystaj z notacji z gwiazdką *

  5. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
DATA = '10.13.37.1      nasa.gov esa.int roscosmos.ru'
Output
ip: str
# 10.13.37.1

hosts: list
# ['nasa.gov', 'esa.int', 'roscosmos.ru']
Hint
  • Use str.split() without any argument

3.8.9.2. Unpacking from nested sequence

English
  1. Use data from "Input" section (see below)

  2. Separate header and records

  3. Use asterisk * notation

  4. Compare result with "Output" section (see below)

Polish
  1. Użyj danych z sekcji "Input" (patrz poniżej)

  2. Oddziel nagłówek i rekordy

  3. Skorzystaj z konstrukcji z gwiazdką *

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
DATA = [
    ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 2.9, 5.6, 1.8, 'virginica'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (4.7, 3.2, 1.3, 0.2, 'setosa'),
    (7.0, 3.2, 4.7, 1.4, 'versicolor'),
    (7.6, 3.0, 6.6, 2.1, 'virginica'),
    (4.9, 3.0, 1.4, 0.2, 'setosa'),
    (4.9, 2.5, 4.5, 1.7, 'virginica'),
    (7.1, 3.0, 5.9, 2.1, 'virginica'),
    (4.6, 3.4, 1.4, 0.3, 'setosa'),
    (5.4, 3.9, 1.7, 0.4, 'setosa'),
    (5.7, 2.8, 4.5, 1.3, 'versicolor'),
    (5.0, 3.6, 1.4, 0.3, 'setosa'),
    (5.5, 2.3, 4.0, 1.3, 'versicolor'),
    (6.5, 3.0, 5.8, 2.2, 'virginica'),
    (6.5, 2.8, 4.6, 1.5, 'versicolor'),
    (6.3, 3.3, 6.0, 2.5, 'virginica'),
    (6.9, 3.1, 4.9, 1.5, 'versicolor'),
    (4.6, 3.1, 1.5, 0.2, 'setosa'),
]
Output
header: tuple
# ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species')

data: list
# [
#   (5.8, 2.7, 5.1, 1.9, 'virginica'),
#   (5.1, 3.5, 1.4, 0.2, 'setosa'),
#   (5.7, 2.8, 4.1, 1.3, 'versicolor'),
#   ...
# ]