3.1. Unpacking Assignment

3.1.1. Recap

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.1.2. Rationale

  • Unpacking and Arbitrary Number of Parameters and Arguments

../../_images/unpacking-assignment,args,params.png

3.1.3. Errors

Note, that set is unordered collection:

{a, b, c} = {1, 2, 3}
# Traceback (most recent call last):
# SyntaxError: can't assign to literal

Too many values to unpack:

a, b, c = [1, 2, 3, 4]
# Traceback (most recent call last):
# ValueError: too many values to unpack (expected 3)

Not enough values to unpack:

a, b, c, d = [1, 2, 3]
# Traceback (most recent call last):
# ValueError: not enough values to unpack (expected 4, got 3)

3.1.4. Arbitrary Number of Arguments

Unpacking values at the right side:

a, b, *c = [1, 2, 3, 4]

a               # 1
b               # 2
c               # [3, 4]

Unpacking values at the left side:

*a, b, c = [1, 2, 3, 4]

a               # [1, 2]
b               # 3
c               # 4

Unpacking values from both sides at once:

a, *b, c = [1, 2, 3, 4]

a               # 1
b               # [2, 3]
c               # 4

Unpacking from variable length:

a, *b, c = [1, 2]

a               # 1
b               # []
c               # 2

Cannot unpack from both sides at once:

*a, b, *c = [1, 2, 3, 4]
# Traceback (most recent call last):
# SyntaxError: two starred expressions in assignment

Unpacking requires values for required arguments:

a, *b, c = [1]
# Traceback (most recent call last):
# ValueError: not enough values to unpack (expected at least 2, got 1)

3.1.5. Nested

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

a               # 1
b               # 2
c               # 3

3.1.6. 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.1.7. Skipping Values

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

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

_ = 'Jan Twardowski'

print(_)
# Jan Twardowski
line = 'Jan,Twardowski,44'

firstname, lastname, _ = line.split(',')

print(firstname)        # Jan
print(lastname)         # Twardowski
a, _, c = 1, 2, 3

print(a)                # 1
print(c)                # 3
_, b, _ = 1, 2, 3

print(b)                # 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, _, _, _, fullname, *_ = line.split(':')

username                # twardowski
fullname               # 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.1.8. Examples

import sys

sys.version_info
# sys.version_info(major=3, minor=9, micro=0, releaselevel='final', serial=0)

major, minor, *_ = sys.version_info
print(major, minor)
# 3.9
*features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')

features                # [5.8, 2.7, 5.1, 1.9]
label                   # 'virginica'
*features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')
avg = sum(features) / len(features)

print(label, avg)
# virginica 3.875
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.1.9. 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.1.10. Assignments

Code 3.34. Solution
"""
* Assignment: Unpacking Assignment Nested
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min

English:
    1. Use data from "Given" section (see below)
    2. Separate header and records
    3. Use asterisk `*` notation
    4. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Odseparuj nagłówek od danych
    3. Skorzystaj z konstrukcji z gwiazdką `*`
    4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Tests:
    >>> type(header)
    <class 'tuple'>
    >>> type(data)
    <class 'list'>
    >>> len(header) > 0
    True
    >>> len(data) > 0
    True
    >>> assert all(type(x) is str for x in header)
    >>> assert all(type(row) is tuple for row in data)
    >>> header
    ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species')
    >>> data  # doctest: +NORMALIZE_WHITESPACE
    [(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')]

"""


# Given
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')]

header: tuple
data: list


Code 3.35. Solution
"""
* Assignment: Unpacking Assignment Flat
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min

English:
    1. Use data from "Given" 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 "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (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ą "Tests" (patrz poniżej)

Hints:
    * Use `str.split()` without any argument

Tests:
    >>> type(ip)
    <class 'str'>
    >>> type(hosts)
    <class 'list'>
    >>> assert all(type(host) is str for host in hosts)
    >>> '' not in hosts
    True
    >>> ip
    '10.13.37.1'
    >>> hosts
    ['nasa.gov', 'esa.int', 'roscosmos.ru']
"""


# Given
DATA = '10.13.37.1      nasa.gov esa.int roscosmos.ru'

ip: str
hosts: list


Code 3.36. Solution
"""
* Assignment: Unpacking Assignment Loop
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Use data from "Given" section (see below)
    2. Iterate over data splitting `*features` from `label`
    3. Define `result: list[str]` with species names ending with "ca" or "osa"
    4. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Iteruj po danych rozdzielając `*features` od `label`
    3. Zdefiniuj `result: list[str]` z nazwami gatunków kończącymi się na "ca" lub "osa"
    4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Hints:
    * `str.endswith()`

Tests:
    >>> type(result)
    <class 'list'>
    >>> len(result) > 0
    True
    >>> assert all(type(x) is str for x in result)
    >>> result
    ['virginica', 'setosa', 'virginica', 'setosa']
"""


# Given
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')]

SUFFIXES = ('ca', 'osa')

result: list