3.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

3.1.1. Recap

  • a = 1 - int

  • a = 1, 2 - tuple

  • a, b = 1, 2 - multiple assignment

  • a, b, c = 1, 2, 3 - multiple assignment

3.1.2. Example

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

3.1.3. Arbitrary Number of Arguments

  • Unpack values at the right side

  • Unpack values at the left side

  • Unpack values from both sides at once

  • Unpack from variable length

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

3.1.4. Errors

  • Cannot unpack from both sides at once

  • Unpack requires values for required arguments

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)

3.1.5. Skipping Values

  • _ is used to skip values

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

  • By convention it is used for data we don't want to access in future

  • It can be used multiple times in the same statement

>>> line = 'Mark,Watney,mwatney@nasa.gov,mwatney@gmail.com'
>>> firstname, lastname, *_ = line.split(',')
>>>
>>> print(f'{firstname=}, {lastname=}')
firstname='Mark', lastname='Watney'
>>> line = 'watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash'
>>> username, _, uid, *_ = line.split(':')
>>>
>>> print(f'{username=}, {uid=}')
username='watney', uid='1000'

3.1.6. For Loop Unpacking

  • Use star expression to unpack values in for loop

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

3.1.7. Multi Dimensional

  • Unpack values from multi-dimensional data

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

3.1.8. Case Study

# avg=3.4, species='virginica'

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.6, 3.1, 1.5, 0.2, 'setosa'),
]

header, *rows = DATA
# header = DATA[0]
# rows = DATA[1:]
# avg=3.4, species='virginica'

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.6, 3.1, 1.5, 0.2, 'setosa'),
]

header, *rows = DATA

for row in rows:
    values = row[:-1]
    species = row[-1]
    avg = sum(values) / len(values)
    print(f'{avg=:.1f}, {species=}')
# avg=3.4, species='virginica'

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.6, 3.1, 1.5, 0.2, 'setosa'),
]

header, *rows = DATA

for row in rows:
    *values, species = row
    avg = sum(values) / len(values)
    print(f'{avg=:.1f}, {species=}')
# avg=3.4, species='virginica'

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.6, 3.1, 1.5, 0.2, 'setosa'),
]

header, *rows = DATA

for *values, species in rows:
    avg = sum(values) / len(values)
    print(f'{avg=:.1f}, {species=}')

3.1.9. Use Case - 1

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

3.1.10. Use Case - 1

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

3.1.11. Use Case - 2

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

3.1.12. Use Case - 3

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

3.1.13. Use Case - 4

  • Python Version

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

3.1.14. Use Case - 5

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

3.1.15. Use Case - 6

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

3.1.16. Use Case - 7

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

3.1.17. Use Case - 8

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

3.1.18. Use Case - 9

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

3.1.19. Use Case - 10

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

3.1.20. Use Case - 11

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

3.1.21. Use Case - 12

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

3.1.22. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment List
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% 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 sys.version_info >= (3, 9), \
'Python 3.9+ required'

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

# Separate ip address from host names
# Use asterisk `*` notation
# type: ip = str # example: '10.13.37.1'
# type: hosts = list # example: ['nasa.gov', 'esa.int', 'polsa.gov.pl']
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment Func
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% English
# 1. Separate ip address from host names
# 2. Use star expression
# 3. Mind, this assignment is very similar
#    to the previous one, but not identical
# 4. Run doctests - all must succeed

# %% Polish
# 1. Odseparuj adres ip od nazwy hostów
# 2. Użyj wyrażenia z gwiazdką
# 3. Zwróć uwagę, że to zadanie jest bardzo podobne
#    do poprzedniego, ale to nie jest identyczne
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `str.split()`

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

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

# Separate ip address from host names
# Use asterisk `*` notation
# type: ip = str # example: '10.13.37.1'
# type: hosts = list # example: ['nasa.gov', 'esa.int', 'polsa.gov.pl']
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment Func
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% 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 sys.version_info >= (3, 9), \
'Python 3.9+ required'

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

# Separate ip address from host names
# Use asterisk `*` notation
# type: ip = str # example: '10.13.37.1'
# type: hosts = list # example: ['nasa.gov', 'esa.int', 'polsa.gov.pl']
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment Nested
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% 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 sys.version_info >= (3, 9), \
'Python 3.9+ required'

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

# Separate values from species name
# Use star expression
# type: values = list[float] # example: [5.1, 3.5, 1.4, 0.2]
# type: species = str # example: 'setosa'
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment Nested
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% 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 sys.version_info >= (3, 9), \
'Python 3.9+ required'

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

# Separate header and rows
# Use star expression
# type: header = tuple[str] # example: ('sepal_length', 'sepal_width', ...)
# type: rows = list[tuple] # [(5.8, 2.7, 5.1, 1.9, 'virginica'),  ...]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Star Assignment Loop
# - Difficulty: easy
# - Lines: 4
# - Minutes: 5

# %% English
# 1. Define `result: list[str]`
# 2. Iterating over `DATA` unpack row to:
#    - `values` - all beside last
#    - `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: list[str]`
# 2. Iterując po `DATA` rozpakuj wiersz do:
#    - `values` - wszystkie poza ostatnim
#    - `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 sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> assert result is not Ellipsis, \
'Assign your result to variable `result`'
>>> assert type(result) is list, \
'Result must be a list'
>>> 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'

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

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