4.7. Idiom Zip¶
Combine two sequences
Generator (lazy evaluated)
zip(*iterables, strict=False)
required
*iterables
- 1 or many sequences or iterator objectIterate over several iterables in parallel, producing tuples with an item from each one.
The zip
object yields n-length tuples, where n is the number of iterables
passed as positional arguments to zip()
. The i-th element in every tuple
comes from the i-th iterable argument to zip()
. This continues until the
shortest argument is exhausted. If strict is true and one of the arguments is
exhausted before the others, raise a ValueError
. [2]
>>> from inspect import isgeneratorfunction, isgenerator
>>>
>>>
>>> isgeneratorfunction(zip)
False
>>>
>>> result = zip(['a','b','c'], [1,2,3])
>>> isgenerator(result)
False
4.7.1. Problem¶
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = []
>>> length = min(len(firstnames), len(lastnames))
>>> i = 0
>>>
>>> while i < length:
... pair = (firstnames[i], lastnames[i])
... result.append(pair)
... i += 1
>>>
>>> result
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Alex', 'Vogel')]
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = []
>>>
>>> for i in range(min(len(firstnames), len(lastnames))):
... pair = (firstnames[i], lastnames[i])
... result.append(pair)
>>>
>>> result
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Alex', 'Vogel')]
4.7.2. Solution¶
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames)
>>>
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Alex', 'Vogel')]
4.7.3. Lazy Evaluation¶
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames)
>>>
>>> next(result)
('Mark', 'Watney')
>>> next(result)
('Melissa', 'Lewis')
>>> next(result)
('Alex', 'Vogel')
>>> next(result)
Traceback (most recent call last):
StopIteration
4.7.4. Generate Dict¶
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames)
>>>
>>> dict(result)
{'Mark': 'Watney', 'Melissa': 'Lewis', 'Alex': 'Vogel'}
>>> roles = ['botanist', 'commander', 'chemist']
>>> names = ['Mark Watney', 'Melissa Lewis', 'Alex Vogel']
>>>
>>> dict(zip(roles, names))
{'botanist': 'Mark Watney',
'commander': 'Melissa Lewis',
'chemist': 'Alex Vogel'}
4.7.5. Adjusts to the Shortest¶
zip()
adjusts to the shortest
>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames)
>>>
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis')]
4.7.6. Adjust to the Longest¶
itertools.zip_longest(iter1 [,iter2 [...]], [fillvalue=None]) --> zip_longest object
>>> from itertools import zip_longest
>>>
>>>
>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>>
>>> list(zip_longest(firstnames, lastnames))
[('Mark', 'Watney'), ('Melissa', 'Lewis'), (None, 'Vogel')]
>>> list(zip_longest(firstnames, lastnames, fillvalue=''))
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('', 'Vogel')]
4.7.7. Three-way merge¶
>>> roles = ['botanist', 'commander', 'chemist']
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(roles, firstnames, lastnames)
>>>
>>> next(result)
('botanist', 'Mark', 'Watney')
>>> next(result)
('commander', 'Melissa', 'Lewis')
>>> next(result)
('chemist', 'Alex', 'Vogel')
>>> next(result)
Traceback (most recent call last):
StopIteration
4.7.8. In For Loop¶
>>> roles = ['botanist', 'commander', 'chemist']
>>> names = ['Mark Watney', 'Melissa Lewis', 'Alex Vogel']
>>>
>>> for role, name in zip(roles, names):
... print(f'{role} -> {name}')
botanist -> Mark Watney
commander -> Melissa Lewis
chemist -> Alex Vogel
4.7.9. Unzip¶
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>>
>>> list(zip(firstnames, lastnames))
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Alex', 'Vogel')]
>>>
>>> fname, lname = zip(*zip(firstnames, lastnames))
>>>
>>> print(fname)
('Mark', 'Melissa', 'Alex')
>>> print(lname)
('Watney', 'Lewis', 'Vogel')
4.7.10. Strict¶
zip(*iterables, strict=False)
Since Python 3.10: PEP 618 -- Add Optional Length-Checking To zip [1]
Source [2]
zip()
adjusts to the shortest:
>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames)
>>>
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis')]
zip()
is often used in cases where the iterables are assumed to be of equal length.
In such cases, it's recommended to use the strict=True
option.
Its output is the same as regular zip()
>>> firstnames = ['Mark', 'Melissa', 'Alex']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>> result = zip(firstnames, lastnames, strict=True)
>>>
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Alex', 'Vogel')]
Unlike the default behavior, it checks that the lengths of iterables are identical, raising a ValueError
if they aren't:
>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
>>>
>>> result = zip(firstnames, lastnames, strict=True)
Traceback (most recent call last):
ValueError: zip() argument 2 is longer than argument 1
Without the strict=True
argument, any bug that results in iterables of different lengths will be silenced, possibly manifesting as a hard-to-find bug in another part of the program.
4.7.11. Zip Longest¶
SetUp:
>>> from itertools import zip_longest
>>>
>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Vogel']
Usage:
>>> result = zip_longest(firstnames, lastnames)
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), (None, 'Vogel')]
>>> result = zip_longest(firstnames, lastnames, fillvalue='n/a')
>>>
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('n/a', 'Vogel')]
4.7.12. Use Case - 0x01¶
>>> for user, address, order in zip(users, addresses, orders):
... print(f'Get {user} orders... {order}')
4.7.13. References¶
4.7.14. Assignments¶
"""
* Assignment: Idiom Zip Dict
* Required: yes
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min
English:
1. Define `result: dict`
2. Assign to `result` zipped `KEYS` and `VALUES` to `dict`
3. Use `zip()`
4. Run doctests - all must succeed
Polish:
1. Zdefiniuj `result: dict`
2. Przypisz do `result` zzipowane `KEYS` i `VALUES` do `dict`
3. Użyj `zip()`
4. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* `dict()`
* `zip()`
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> assert type(result) is dict, \
'Variable `result` has invalid type, should be dict'
>>> assert all(type(x) is str for x in result.keys()), \
'All dict keys should be str'
>>> assert 'sepal_length' in result.keys()
>>> assert 'sepal_width' in result.keys()
>>> assert 'petal_length' in result.keys()
>>> assert 'petal_width' in result.keys()
>>> assert 'species' in result.keys()
>>> assert 5.8 in result.values()
>>> assert 2.7 in result.values()
>>> assert 5.1 in result.values()
>>> assert 1.9 in result.values()
>>> assert 'virginica' in result.values()
>>> result # doctest: +NORMALIZE_WHITESPACE
{'sepal_length': 5.8,
'sepal_width': 2.7,
'petal_length': 5.1,
'petal_width': 1.9,
'species': 'virginica'}
"""
KEYS = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
VALUES = [5.8, 2.7, 5.1, 1.9, 'virginica']
# Dict with Zipped KEYS and VALUES
# type: dict[str,float|str]
result = ...
"""
* Assignment: Idiom Zip List[Dict]
* Complexity: easy
* Lines of code: 2 lines
* Time: 5 min
English:
1. Define `result: list[dict]`:
2. Convert `DATA` from `list[tuple]` to `list[dict]`
a. key - name from the header
b. value - numerical value or species name
3. Run doctests - all must succeed
Polish:
1. Zdefiniuj `result: list[dict]`:
2. Przekonwertuj `DATA` z `list[tuple]` do `list[dict]`
a. klucz - nazwa z nagłówka
b. wartość - wartość numeryczna lub nazwa gatunku
3. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from pprint import pprint
>>> assert result is not Ellipsis, \
'Assign result to variable: `result`'
>>> result = list(result)
>>> assert type(result) is list, \
'Result must be a list'
>>> assert len(result) > 0, \
'Result cannot be empty'
>>> assert all(type(element) is dict for element in result), \
'All elements in result must be a dict'
>>> pprint(result)
[{'petal_length': 5.1,
'petal_width': 1.9,
'sepal_length': 5.8,
'sepal_width': 2.7,
'species': 'virginica'},
{'petal_length': 1.4,
'petal_width': 0.2,
'sepal_length': 5.1,
'sepal_width': 3.5,
'species': 'setosa'},
{'petal_length': 4.1,
'petal_width': 1.3,
'sepal_length': 5.7,
'sepal_width': 2.8,
'species': 'versicolor'},
{'petal_length': 5.6,
'petal_width': 1.8,
'sepal_length': 6.3,
'sepal_width': 2.9,
'species': 'virginica'},
{'petal_length': 4.5,
'petal_width': 1.5,
'sepal_length': 6.4,
'sepal_width': 3.2,
'species': 'versicolor'},
{'petal_length': 1.3,
'petal_width': 0.2,
'sepal_length': 4.7,
'sepal_width': 3.2,
'species': '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'),
]
# Convert DATA from list[tuple] to list[dict]
# type: list[dict[str,float|str]]
result = ...