2.1. Star Assignment

  • a, b, *c = 1, 2, 3, 4, 5

  • Used when there is arbitrary number of values to unpack

  • Could be used from start, middle, end

  • There can't be multiple star expressions in one assignment statement

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

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

../../_images/unpack-assignment%2Cargs%2Cparams.png

2.1.1. Example

>>> def get_user_details(username):
...     return 'Mark', 'Watney', 'mwatney@nasa.gov', 'mwatney@esa.int', 'mwatney@polsa.gov.pl'
>>>
>>>
>>> firstname, lastname, *email = get_user_details('mwatney')
>>>
>>> firstname
'Mark'
>>>
>>> lastname
'Watney'
>>>
>>> email
['mwatney@nasa.gov', 'mwatney@esa.int', 'mwatney@polsa.gov.pl']

2.1.2. Arbitrary Number of Arguments

Unpack values at the right side:

>>> a, b, *c = [1, 2, 3, 4, 5]
>>>
>>> print(f'{a=}, {b=}, {c=}')
a=1, b=2, c=[3, 4, 5]

Unpack values at the left side:

>>> *a, b, c = [1, 2, 3, 4, 5]
>>>
>>> print(f'{a=}, {b=}, {c=}')
a=[1, 2, 3], b=4, c=5

Unpack values from both sides at once:

>>> a, *b, c = [1, 2, 3, 4, 5]
>>>
>>> print(f'{a=}, {b=}, {c=}')
a=1, b=[2, 3, 4], c=5

Unpack from variable length:

>>> a, *b, c = [1, 2]
>>>
>>> print(f'{a=}, {b=}, {c=}')
a=1, b=[], c=2

2.1.3. Errors

Cannot unpack from both sides at once:

>>> *a, b, *c = [1, 2, 3, 4, 5]
Traceback (most recent call last):
SyntaxError: multiple 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.1.4. 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

>>> _ = 'Mark Watney'
>>>
>>> print(_)
Mark Watney
>>> line = 'Mark,Watney,mwatney@nasa.gov,mwatney@esa.int,mwatney@polsa.gov.pl'
>>> firstname, lastname, *_ = line.split(',')
>>>
>>> print(f'{firstname=}, {lastname=}')
firstname='Mark', lastname='Watney'
>>> line = '4.9,3.1,1.5,0.1,setosa'
>>> *_, label = line.split(',')
>>>
>>> print(f'{label=}')
label='setosa'
>>> line = 'watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash'
>>> username, _, uid, *_ = line.split(':')
>>>
>>> print(f'{username=}, {uid=}')
username='watney', uid='1000'

2.1.5. For Loop Unpacking

>>> 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 row in DATA:
...     print(f'{row=}')
...
row=(5.8, 2.7, 5.1, 1.9, 'virginica')
row=(5.1, 3.5, 1.4, 0.2, 'setosa')
row=(5.7, 2.8, 4.1, 1.3, 'versicolor')
>>> for row in DATA:
...     values = row[0:4]
...     species = row[-1]
...     print(f'{values=}, {species=}')
...
values=(5.8, 2.7, 5.1, 1.9), species='virginica'
values=(5.1, 3.5, 1.4, 0.2), species='setosa'
values=(5.7, 2.8, 4.1, 1.3), species='versicolor'
>>> for row in DATA:
...     *values, species = row
...     print(f'{values=}, {species=}')
...
values=[5.8, 2.7, 5.1, 1.9], species='virginica'
values=[5.1, 3.5, 1.4, 0.2], species='setosa'
values=[5.7, 2.8, 4.1, 1.3], species='versicolor'
>>> for *values, species in DATA:
...     print(f'{values=}, {species=}')
...
values=[5.8, 2.7, 5.1, 1.9], species='virginica'
values=[5.1, 3.5, 1.4, 0.2], species='setosa'
values=[5.7, 2.8, 4.1, 1.3], species='versicolor'

2.1.6. Multi Dimensional

>>> from pprint import pprint
>>> 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'),
... ]
>>> header = DATA[0]
>>> rows = DATA[1:]
>>>
>>> pprint(header)
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species')
>>>
>>> pprint(rows)
[(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')]
>>> header, *rows = DATA
>>>
>>> pprint(header)
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species')
>>>
>>> pprint(rows)
[(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')]

2.1.7. Merge

>>> def echo(**a, **b):
...     return locals()
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

2.1.8. Use Case - 0x01

>>> a, b, c = range(0, 3)
>>> a, b, c, d, e = range(0, 5)
>>> a, b, *c = range(0, 10)

2.1.9. Use Case - 0x01

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

2.1.10. Use Case - 0x02

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

2.1.11. Use Case - 0x03

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

2.1.12. Use Case - 0x04

  • Python Version

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

2.1.13. Use Case - 0x05

  • Iris 1D

>>> *values, species = (5.8, 2.7, 5.1, 1.9, 'virginica')
>>>
>>> print(f'{values=}, {species=}')
values=[5.8, 2.7, 5.1, 1.9], species='virginica'

2.1.14. Use Case - 0x06

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

2.1.15. Use Case - 0x07

>>> line = '1969-07-21, 02:56:15, WARNING, Neil Armstrong first words on the Moon'
>>> d, t, lvl, *msg = line.split(', ')
>>>
>>> d
'1969-07-21'
>>> t
'02:56:15'
>>> lvl
'WARNING'
>>> msg
['Neil Armstrong first words on the Moon']

2.1.16. Use Case - 0x08

>>> line = 'watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash'
>>> username, password, uid, *others = line.split(':')
>>>
>>> username
'watney'
>>> password
'x'
>>> uid
'1000'
>>> others
['1000', 'Mark Watney', '/home/watney', '/bin/bash']

2.1.17. Use Case - 0x09

>>> line = 'watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash'
>>> username, _, uid, *_ = line.split(':')
>>>
>>> username
'watney'
>>> uid
'1000'

2.1.18. Use Case - 0x0A

>>> line = '4.9,3.1,1.5,0.1,setosa'
>>> *values, species = line.split(',')
>>> values
['4.9', '3.1', '1.5', '0.1']
>>> species
'setosa'

2.1.19. Use Case - 0x0B

>>> data = (5.8, 2.7, 5.1, 1.9, 'virginica')
>>> *values, species = data
>>> values
[5.8, 2.7, 5.1, 1.9]
>>> species
'virginica'

2.1.20. Use Case - 0x0C

  • 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 *values, species in DATA:
...     avg = sum(values) / len(values)
...     print(f'{avg=:.2f} {species=}')
avg=3.88 species='virginica'
avg=2.55 species='setosa'
avg=3.48 species='versicolor'

2.1.21. Assignments

Code 2.10. Solution
"""
* Assignment: Star Assignment List
* Type: class assignment
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

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

Polish:
    1. Odseparuj adres ip od nazwy 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 your result to variable `ip`'
    >>> assert hosts is not Ellipsis, \
    'Assign your 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', 'polsa.gov.pl']
"""

DATA = ['10.13.37.1', 'nasa.gov', 'esa.int', 'polsa.gov.pl']

# String with IP address
# example: '10.13.37.1'
# type: str
ip = ...

# List of hosts
# example: ['nasa.gov', 'esa.int', 'polsa.gov.pl']
# type: list[str]
hosts = ...

Code 2.11. Solution
"""
* Assignment: Star Assignment Func
* Type: class assignment
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Separate ip address from host names
    2. Use star expression
    3. Run doctests - all must succeed

Polish:
    1. Odseparuj adres ip od nazwy hostów
    2. Użyj wyrażenia z gwiazdką
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `str.split()`

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

    >>> assert ip is not Ellipsis, \
    'Assign your result to variable `ip`'
    >>> assert hosts is not Ellipsis, \
    'Assign your 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', 'polsa.gov.pl']
"""

DATA = '10.13.37.1 nasa.gov esa.int polsa.gov.pl'

# String with IP address: '10.13.37.1'
# type: str
ip = ...

# List of host names: ['nasa.gov', 'esa.int', 'polsa.gov.pl']
# type: list[str]
hosts = ...

Code 2.12. Solution
"""
* Assignment: Star Assignment Nested
* Type: class assignment
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Separate values from species name
    2. Use star expression
    3. Run doctests - all must succeed

Polish:
    1. Odseparuj wartości od nazwy gatunku
    2. Użyj wyrażenia z gwiazdką
    3. Uruchom doctesty - wszystkie muszą się powieść

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

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

    >>> values
    [5.1, 3.5, 1.4, 0.2]

    >>> species
    'setosa'
"""

DATA = (5.1, 3.5, 1.4, 0.2, 'setosa')

# All numeric values from DATA
# type: list[float]
values = ...

# species name from DATA (last element)
# type: str
species = ...

Code 2.13. Solution
"""
* Assignment: Star Assignment Nested
* Type: class assignment
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Separate header and rows
    2. Use star expression
    3. Run doctests - all must succeed

Polish:
    1. Odseparuj nagłówek od wierszy danych
    2. Użyj wyrażenia 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 rows is not Ellipsis, \
    'Assign result to variable: `rows`'
    >>> assert len(header) > 0, \
    'Variable `header` cannot be empty'
    >>> assert len(rows) > 0, \
    'Variable `rows` cannot be empty'
    >>> assert type(header) is tuple, \
    'Variable `header` has invalid type, should be tuple'
    >>> assert type(rows) 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 rows), \
    'All rows in `rows` should be tuple'

    >>> header
    ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species')

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

# first line from DATA
# example: ('sepal_length', 'sepal_width', ...)
# type: tuple[str]
header = ...

# all the other lines from DATA, beside first line
# example: [(5.8, 2.7, 5.1, 1.9, 'virginica'),  ...]
# type: list[tuple]
rows = ...

Code 2.14. Solution
"""
* Assignment: Star Assignment Loop
* Type: class assignment
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Define `result: set[str]`
    2. Iterating over `DATA` unpack row to:
        a. `values` - all beside last
        b. `species` - last in the row
    3. Append to `result` species names with suffix in `SUFFIXES`
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: set[str]`
    2. Iterując po `DATA` rozpakuj wiersz do:
        a. `values` - wszystkie poza ostatnim
        b. `species` - ostatni w wierszu
    3. Dodaj do `result` nazwy gatunków (species) z końcówką w `SUFFIXES`
    4. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `str.endswith()`

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

    >>> assert result is not Ellipsis, \
    'Assign your result to variable `result`'
    >>> assert type(result) is set, \
    'Result must be a set'
    >>> assert len(result) > 0, \
    'Result cannot be empty'
    >>> assert all(type(element) is str for element in result), \
    'All elements in result must be a str'

    >>> 'virginica' in result
    True
    >>> 'setosa' in result
    True
    >>> 'versicolor' in result
    False
"""

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

# species names with suffix in `SUFFIXES`
# type: set[str]
result = ...