2.2. Unpack Assignment Star

2.2.1. Rationale

../../_images/unpack-assignment,args,params1.png

2.2.2. Arbitrary Number of Arguments

Unpack values at the right side:

>>> a, b, *c = [1, 2, 3, 4]
>>>
>>>
>>> print(a)
1
>>>
>>> print(b)
2
>>>
>>> print(c)
[3, 4]

Unpack values at the left side:

>>> *a, b, c = [1, 2, 3, 4]
>>>
>>>
>>> print(a)
[1, 2]
>>>
>>> print(b)
3
>>>
>>> print(c)
4

Unpack values from both sides at once:

>>> a, *b, c = [1, 2, 3, 4]
>>>
>>>
>>> print(a)
1
>>>
>>> print(b)
[2, 3]
>>>
>>> print(c)
4

Unpack from variable length:

>>> a, *b, c = [1, 2]
>>>
>>>
>>> print(a)
1
>>> print(b)
[]
>>> print(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

Unpack 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)

2.2.3. 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,1,2,3,4,5'
>>> firstname, lastname, *_ = line.split(',')
>>>
>>>
>>> print(firstname)
Jan
>>>
>>> print(lastname)
Twardowski
>>> line = '4.9,3.1,1.5,0.1,setosa'
>>> *_, label = line.split(',')
>>>
>>> print(label)
setosa
>>> line = 'twardowski:x:1001:1001:Jan Twardowski:/home/twardowski:/bin/bash'
>>> username, *_, home, _ = line.split(':')
>>>
>>> print(username)
twardowski
>>>
>>> print(home)
/home/twardowski

2.2.4. Use Case - 0x01

>>> line = 'ares3,watney,lewis,vogel,johanssen'
>>> mission, *crew = line.split(',')
>>>
>>>
>>> print(mission)
ares3
>>>
>>> print(crew)
['watney', 'lewis', 'vogel', 'johanssen']

2.2.5. Use Case - 0x02

>>> first, *middle, last = [1, 2, 3, 4]
>>>
>>>
>>> print(first)
1
>>>
>>> print(middle)
[2, 3]
>>>
>>> print(last)
4
>>> first, second, *others = [1, 2, 3, 4]
>>>
>>>
>>> print(first)
1
>>>
>>> print(second)
2
>>>
>>> print(others)
[3, 4]

2.2.6. Use Case - 0x03

>>> first, second, *others = range(0,10)
>>>
>>>
>>> print(first)
0
>>>
>>> print(second)
1
>>>
>>> print(others)
[2, 3, 4, 5, 6, 7, 8, 9]
>>> first, second, *_ = range(0,10)
>>>
>>>
>>> print(first)
0
>>>
>>> print(second)
1

2.2.7. Use Case - 0x04

  • Python Version

>>> import sys
>>>
>>>
>>> major, minor, *_ = sys.version_info
>>>
>>> print(major, minor, sep='.')
3.10

2.2.8. Use Case - 0x05

  • Iris 1D

>>> *features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')
>>>
>>>
>>> print(features)
[5.8, 2.7, 5.1, 1.9]
>>>
>>> print(label)
virginica

2.2.9. Use Case - 0x06

>>> *features, label = (5.8, 2.7, 5.1, 1.9, 'virginica')
>>> avg = sum(features) / len(features)
>>>
>>>
>>> print(f'{avg=:.2f}, {label=}')
avg=3.88, label='virginica'

2.2.10. Use Case - 0x07

  • Iris 2D

>>> 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(f'{avg=:.2f} {label=}')
avg=3.88 label='virginica'
avg=2.55 label='setosa'
avg=3.48 label='versicolor'

2.2.11. Assignments

Code 2.13. Solution
"""
* Assignment: Unpack Star List
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Separate ip address and host names
    2. Use asterisk `*` notation
    3. Run doctests - all must succeed

Polish:
    1. Odseparuj adres ip i nazw hostów
    2. Skorzystaj z notacji z gwiazdką `*`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert ip is not Ellipsis, \
    'Assign result to variable: `ip`'
    >>> assert hosts is not Ellipsis, \
    'Assign result to variable: `hosts`'
    >>> assert type(ip) is str, \
    'Variable `ip` has invalid type, should be str'
    >>> assert type(hosts) is list, \
    'Variable `hosts` has invalid type, should be list'
    >>> assert all(type(x) is str for x in hosts), \
    'All rows in `hosts` should be str'
    >>> assert '' not in hosts, \
    'Do not pass any arguments to str.split() method'

    >>> ip
    '10.13.37.1'

    >>> hosts
    ['nasa.gov', 'esa.int', 'roscosmos.ru']
"""

DATA = ['10.13.37.1', 'nasa.gov', 'esa.int', 'roscosmos.ru']

# str: IP address
ip = ...

# list[str]: list of hostnames
hosts = ...

Code 2.14. Solution
"""
* Assignment: Unpack Star Func
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Using `str.split()` split input data by white space
    2. Separate ip address and host names
    3. Use asterisk `*` notation
    4. Run doctests - all must succeed

Polish:
    1. Używając `str.split()` podziel dane wejściowe po białych znakach
    2. Odseparuj adres ip i nazw hostów
    3. Skorzystaj z notacji z gwiazdką `*`
    4. Uruchom doctesty - wszystkie muszą się powieść

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

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert ip is not Ellipsis, \
    'Assign result to variable: `ip`'
    >>> assert hosts is not Ellipsis, \
    'Assign result to variable: `hosts`'
    >>> assert type(ip) is str, \
    'Variable `ip` has invalid type, should be str'
    >>> assert type(hosts) is list, \
    'Variable `hosts` has invalid type, should be list'
    >>> assert all(type(x) is str for x in hosts), \
    'All rows in `hosts` should be str'
    >>> assert '' not in hosts, \
    'Do not pass any arguments to str.split() method'

    >>> ip
    '10.13.37.1'

    >>> hosts
    ['nasa.gov', 'esa.int', 'roscosmos.ru']
"""

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

# str: IP address
ip = ...

# list[str]: list of hostnames
hosts = ...

Code 2.15. Solution
"""
* Assignment: Unpack Star Nested
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Separate header and records
    2. Use asterisk `*` notation
    3. Run doctests - all must succeed

Polish:
    1. Odseparuj nagłówek od danych
    2. Skorzystaj z konstrukcji z gwiazdką `*`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert header is not Ellipsis, \
    'Assign result to variable: `header`'
    >>> assert data is not Ellipsis, \
    'Assign result to variable: `data`'
    >>> assert len(header) > 0, \
    'Variable `header` cannot be empty'
    >>> assert len(data) > 0, \
    'Variable `data` cannot be empty'
    >>> assert type(header) is tuple, \
    'Variable `header` has invalid type, should be tuple'
    >>> assert type(data) is list, \
    'Variable `hosts` has invalid type, should be list'
    >>> assert all(type(x) is str for x in header), \
    'All rows in `header` should be str'
    >>> assert all(type(x) is tuple for x in data), \
    'All rows in `data` should be tuple'

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

"""

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

# tuple[str]: first line from DATA
header = ...

# list[tuple]: all the other lines from DATA, beside first line
data = ...


Code 2.16. Solution
"""
* Assignment: Unpack Star Loop
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Iterate over data splitting `*features` from `label`
    2. Define `result: list[str]` with species names ending with "ca" or "osa"
    3. Run doctests - all must succeed

Polish:
    1. Iteruj po danych rozdzielając `*features` od `label`
    2. Zdefiniuj `result: list[str]` z nazwami gatunków kończącymi się na "ca" lub "osa"
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `str.endswith()`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert len(result) > 0, \
    'Variable `result` cannot be empty'
    >>> assert type(result) is list, \
    'Variable `result` has invalid type, should be list'
    >>> assert all(type(x) is str for x in result), \
    'All rows in `result` should be str'

    >>> result
    ['virginica', 'setosa', 'virginica', 'setosa']
"""

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')

# list[str]: species names ending with "ca" or "osa"
result = ...