# 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

## 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(':')
>>>


## 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:]
>>>
('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
>>>
('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'
>>>
'watney'
'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(':')
>>>
'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¶

"""
* Assignment: Star Assignment List
* Required: yes
* 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']

# example: '10.13.37.1'
# type: str
ip = ...

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


"""
* Assignment: Star Assignment Func
* Required: yes
* 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 = ...


"""
* Assignment: Star Assignment Nested
* Required: yes
* 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 = ...


"""
* Assignment: Star Assignment Nested
* Required: yes
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

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

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

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


"""
* Assignment: Star Assignment Loop
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
1. Iterate over data splitting values from species
2. Define result: list[str] with
species names ending with "ca" or "osa"
3. Use star expression
4. Run doctests - all must succeed

Polish:
1. Iteruj po danych rozdzielając values od species
2. Zdefiniuj result: list[str] z
nazwami gatunków kończącymi się na "ca" lub "osa"
3. Użyj wyrażenia z gwiazdką
4. 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')

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