3.1. Star Assignment

  • Arbitrary Number of Arguments

  • 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

Example:

>>> firstname, lastname, email_work, email_school = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
>>>
>>> firstname
'Alice'
>>>
>>> lastname
'Apricot'
>>>
>>> email_work
'alice@example.com'
>>>
>>> email_school
'alice@example.edu'

3.1.2. Unpack Right

  • Unpack values at the right side

  • firstname, lastname, *emails = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')

>>> firstname, lastname, *emails = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
>>>
>>> firstname
'Alice'
>>>
>>> lastname
'Apricot'
>>>
>>> emails
['alice@example.com', 'alice@example.edu']

3.1.3. Unpack Left

  • Unpack values at the left side

  • *name, email_work, email_school = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')

>>> *name, email_work, email_school = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
>>>
>>> name
['Alice', 'Apricot']
>>>
>>> email_work
'alice@example.com'
>>>
>>> email_school
'alice@example.edu'

3.1.4. Unpack Middle

  • Unpack values from the middle

>>> firstname, *others, email_school = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
>>>
>>> firstname
'Alice'
>>>
>>> others
['Apricot', 'alice@example.com']
>>>
>>> email_school
'alice@example.edu'

3.1.5. Errors

  • Cannot unpack from both sides at once

Cannot unpack from both sides at once:

>>> *name, *email = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
Traceback (most recent call last):
SyntaxError: multiple starred expressions in assignment

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

>>> firstname, lastname, *_ = ('Alice', 'Apricot', 'alice@example.com', 'alice@example.edu')
>>>
>>> firstname
'Alice'
>>>
>>> lastname
'Apricot'

3.1.7. Multi Dimensional

  • Unpack values from multi-dimensional data

>>> DATA = [
...     ('firstname', 'lastname', 'age'),
...     ('Alice', 'Apricot', 30),
...     ('Bob', 'Blackthorn', 31),
...     ('Carol', 'Corn', 32),
...     ('Dave', 'Durian', 33),
...     ('Eve', 'Elderberry', 34),
...     ('Mallory', 'Melon', 15),
... ]

Writing:

>>> header = DATA[0]
>>> rows = DATA[1:]

Has the same effect as:

>>> header, *rows = DATA

Results:

>>> header
('firstname', 'lastname', 'age')
>>>
>>> rows
[('Alice', 'Apricot', 30),
 ('Bob', 'Blackthorn', 31),
 ('Carol', 'Corn', 32),
 ('Dave', 'Durian', 33),
 ('Eve', 'Elderberry', 34),
 ('Mallory', 'Melon', 15)]

3.1.8. For Loop Unpacking

  • Use star expression to unpack values in for loop

>>> DATA = [
...     ('firstname', 'lastname', 'age'),
...     ('Alice', 'Apricot', 30),
...     ('Bob', 'Blackthorn', 31),
...     ('Carol', 'Corn', 32),
...     ('Dave', 'Durian', 33),
...     ('Eve', 'Elderberry', 34),
...     ('Mallory', 'Melon', 15),
... ]
>>> for firstname, lastname, age in DATA[1:]:
...     print(firstname)
...
Alice
Bob
Carol
Dave
Eve
Mallory
>>> for firstname, *_ in DATA[1:]:
...     print(firstname)
...
Alice
Bob
Carol
Dave
Eve
Mallory

3.1.9. Recap

  • 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

3.1.10. 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.11. 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.12. Use Case - 1

>>> line = 'staff,alice,bob,carol,dave,eve'
>>> group, *members = line.split(',')
>>>
>>> print(f'{group=}, {members=}')
group='staff', members=['alice', 'bob', 'carol', 'dave', 'eve']

3.1.13. 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.14. 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.15. Use Case - 4

  • 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.16. Use Case - 5

>>> *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.17. Use Case - 6

>>> 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.18. Use Case - 7

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

3.1.19. Use Case - 8

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

3.1.20. Use Case - 9

>>> 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.21. Use Case - 10

>>> 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.22. Use Case - 11

  • 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.23. Assignments

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

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

# %% English
# 1. Use star expression to separate IP address from hosts
# 2. Define variable `ip` with IP address
# 3. Define variable `hosts` with list of hosts
# 4. Run doctests - all must succeed

# %% Polish
# 1. Użyj wyrażenia z gwiazdką do odseparowania adresu ip od hostów
# 2. Zdefiniuj zmienną `ip` z adresem ip
# 3. Zdefiniuj zmienną `hosts` z listą hostów
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> ip
# '127.0.0.1'
#
# >>> hosts
# ['example.com', 'example.net', 'example.org']

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert ip is not Ellipsis, \
'Variable `ip` has an invalid value; assign result of your program to it.'

>>> assert hosts is not Ellipsis, \
'Variable `hosts` has an invalid value; assign result of your program to it.'

>>> assert type(ip) is str, \
'Variable `ip` has an invalid type; expected: `str`.'

>>> assert type(hosts) is list, \
'Variable `hosts` has an invalid type; expected: `list`.'

>>> assert all(type(x) is str for x in hosts), \
'Variable `hosts` has elements of an invalid type; all items should be: `str`.'

>>> assert '' not in hosts, \
'Do not pass any arguments to str.split() method'

>>> ip
'127.0.0.1'

>>> hosts
['example.com', 'example.net', 'example.org']
"""

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

# %% Imports

# %% Types
ip: str
hosts: list[str]

# %% Data
DATA = ['127.0.0.1', 'example.com', 'example.net', 'example.org']

# %% Result

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

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

# %% English
# 0. Mind, this assignment is very similar to the previous one,
#    but input data (`DATA`) is a bit different
# 1. Use star expression to separate IP address from hosts
# 2. Define variable `ip` with IP address
# 3. Define variable `hosts` with list of hosts
# 4. Run doctests - all must succeed

# %% Polish
# 0. Zwróć uwagę, że to zadanie jest bardzo podobne do poprzedniego,
#    ale dane wejściowe (`DATA`) są trochę inne
# 1. Użyj wyrażenia z gwiazdką do odseparowania adresu ip od hostów
# 2. Zdefiniuj zmienną `ip` z adresem ip
# 3. Zdefiniuj zmienną `hosts` z listą hostów
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> ip
# '127.0.0.1'
#
# >>> hosts
# ['example.com', 'example.net', 'example.org']

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

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert ip is not Ellipsis, \
'Variable `ip` has an invalid value; assign result of your program to it.'

>>> assert hosts is not Ellipsis, \
'Variable `hosts` has an invalid value; assign result of your program to it.'

>>> assert type(ip) is str, \
'Variable `ip` has an invalid type; expected: `str`.'

>>> assert type(hosts) is list, \
'Variable `hosts` has an invalid type; expected: `list`.'

>>> assert all(type(x) is str for x in hosts), \
'Variable `hosts` has elements of an invalid type; all items should be: `str`.'

>>> assert '' not in hosts, \
'Do not pass any arguments to str.split() method'

>>> ip
'127.0.0.1'

>>> hosts
['example.com', 'example.net', 'example.org']
"""

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

# %% Imports

# %% Types
ip: str
hosts: list[str]

# %% Data
DATA = '127.0.0.1 example.com example.net example.org'

# %% Result

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

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

# %% English
# 1. Use star expression to separate values from species name
# 2. Define variable `values` with numerical values
# 3. Define variable `species` with species name
# 4. Run doctests - all must succeed

# %% Polish
# 1. Użyj wyrażenia z gwiazdką do odseparowania wartości od nazwy gatunku
# 2. Zdefiniuj zmienną `values` z numerycznymi wartościami
# 3. Zdefiniuj zmienną `species` z nazwą gatunku
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> values
# [5.1, 3.5, 1.4, 0.2]
#
# >>> species
# 'setosa'

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert values is not Ellipsis, \
'Variable `values` has an invalid value; assign result of your program to it.'

>>> assert species is not Ellipsis, \
'Variable `species` has an invalid value; assign result of your program to it.'

>>> assert len(values) > 0, \
'Variable `values` has an invalid length; expected more than zero elements.'

>>> assert len(species) > 0, \
'Variable `species` has an invalid length; expected more than zero elements.'

>>> assert type(values) is list, \
'Variable `values` has an invalid type; expected: `list`.'

>>> assert type(species) is str, \
'Variable `species` has an invalid type; expected: `str`.'

>>> assert all(type(x) is float for x in values), \
'Variable `values` has elements of an invalid type; all items should be: `float`.'

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

>>> species
'setosa'
"""

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

# %% Imports

# %% Types
values: list[float]
species: str

# %% Data
DATA = (5.1, 3.5, 1.4, 0.2, 'setosa')

# %% Result

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

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

# %% English
# 1. Use star expression to separate header from rows
# 2. Define variable `header` with header (first row)
# 3. Define variable `rows` with all the other rows
# 4. Run doctests - all must succeed

# %% Polish
# 1. Użyj wyrażenia z gwiazdką do odseparowania nagłówka od wierszy
# 2. Zdefiniuj zmienną `header` z nagłówkiem (pierwszy wiersz)
# 3. Zdefiniuj zmienną `rows` z pozostałymi wierszami
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> header
# ('firstname', 'lastname', 'age')
#
# >>> rows
# [('Alice', 'Apricot', 30),
#  ('Bob', 'Blackthorn', 31),
#  ('Carol', 'Corn', 32),
#  ('Dave', 'Durian', 33),
#  ('Eve', 'Elderberry', 34),
#  ('Mallory', 'Melon', 15)]

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert header is not Ellipsis, \
'Variable `header` has an invalid value; assign result of your program to it.'

>>> assert rows is not Ellipsis, \
'Variable `rows` has an invalid value; assign result of your program to it.'

>>> assert len(header) > 0, \
'Variable `header` has an invalid length; expected more than zero elements.'

>>> assert len(rows) > 0, \
'Variable `rows` has an invalid length; expected more than zero elements.'

>>> assert type(header) is tuple, \
'Variable `header` has an invalid type; expected: `tuple`.'

>>> assert type(rows) is list, \
'Variable `rows` has an invalid type; expected: `list`.'

>>> assert all(type(x) is str for x in header), \
'Variable `header` has elements of an invalid type; all items should be: `str`.'

>>> assert all(type(x) is tuple for x in rows), \
'Variable `rows` has elements of an invalid type; all items should be: `tuple`.'

>>> header
('firstname', 'lastname', 'age')

>>> from pprint import pprint
>>> pprint(rows)
[('Alice', 'Apricot', 30),
 ('Bob', 'Blackthorn', 31),
 ('Carol', 'Corn', 32),
 ('Dave', 'Durian', 33),
 ('Eve', 'Elderberry', 34),
 ('Mallory', 'Melon', 15)]
"""

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

# %% Imports

# %% Types
header: tuple[str, str, str]
rows: list[tuple[str, str, str]]

# %% Data
DATA = [
    ('firstname', 'lastname', 'age'),
    ('Alice', 'Apricot', 30),
    ('Bob', 'Blackthorn', 31),
    ('Carol', 'Corn', 32),
    ('Dave', 'Durian', 33),
    ('Eve', 'Elderberry', 34),
    ('Mallory', 'Melon', 15),
]

# %% Result

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

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

# %% English
# 1. Define `result: list[str]` with all email addresses having domain name in `DOMAINS`
# 2. Use star unpack in for loop
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj `result: list[str]` z wszystkimi adresami email mającymi domenę z `DOMAINS`
# 2. Użyj rozpakowywania z gwiazdką w pętli for
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> result
# ['alice@example.com',
#  'bob@example.com',
#  'carol@example.com',
#  'mallory@example.net']

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

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> from pprint import pprint

>>> assert 'result' in globals(), \
'Variable `result` is not defined; assign result of your program to it.'

>>> assert result is not Ellipsis, \
'Variable `result` has an invalid value; assign result of your program to it.'

>>> assert type(result) is list, \
'Variable `result` has an invalid type; expected: `list`.'

>>> assert len(result) > 0, \
'Variable `result` has an invalid length; expected more than zero elements.'

>>> assert all(type(x) is str for x in result), \
'Variable `result` has elements of an invalid type; all items should be: `str`.'

>>> pprint(result)
['alice@example.com',
 'bob@example.com',
 'carol@example.com',
 'mallory@example.net']
"""

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

# %% Imports

# %% Types
result: list[str]

# %% Data
DATA = [
    ('firstname', 'lastname', 'email'),
    ('Alice', 'Apricot', 'alice@example.com'),
    ('Bob', 'Blackthorn', 'bob@example.com'),
    ('Carol', 'Corn', 'carol@example.com'),
    ('Dave', 'Durian', 'dave@example.org'),
    ('Eve', 'Elderberry', 'eve@example.org'),
    ('Mallory', 'Melon', 'mallory@example.net'),
]

DOMAINS = ('example.com', 'example.net')

# %% Result
result = ...