9.5. Iterator Zip

  • Combine two or more sequences

  • Lazy evaluated

  • zip(*iterables, strict=False)

  • required *iterables - 1 or many sequences or iterator object

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

9.5.1. Problem

Using while loop:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = []
>>> length = min(len(data1), len(data2))
>>> i = 0
>>> while i < length:
...     a = data1[i]
...     b = data2[i]
...     result.append((a,b))
...     i += 1

Using for loop:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = []
>>> count = min(len(data1), len(data2))
>>> for i in range(count):
...     a = data1[i]
...     b = data2[i]
...     result.append((a,b))

9.5.2. Solution

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(data1, data2)

9.5.3. Lazy Evaluation

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(data1, data2)
>>>
>>> next(result)
('Mark', 'Watney')
>>>
>>> next(result)
('Melissa', 'Lewis')
>>>
>>> next(result)
('Rick', 'Martinez')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

9.5.4. Iteration

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> for result in zip(data1, data2):
...     print(result)
('Mark', 'Watney')
('Melissa', 'Lewis')
('Rick', 'Martinez')

9.5.5. Unpacking

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> for firstname, lastname in zip(data1, data2):
...     print(f'{firstname=}, {lastname=}')
firstname='Mark', lastname='Watney'
firstname='Melissa', lastname='Lewis'
firstname='Rick', lastname='Martinez'

9.5.6. As List

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(data1, data2)
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', 'Martinez')]

9.5.7. As Dict

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(data1, data2)
>>> dict(result)
{'Mark': 'Watney', 'Melissa': 'Lewis', 'Rick': 'Martinez'}

9.5.8. Many Iterables

Lazy Evaluation:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>> data3 = ['botanist', 'commander', 'pilot']
>>>
>>> result = zip(data1, data2, data3)
>>>
>>> next(result)
('Mark', 'Watney', 'botanist')
>>>
>>> next(result)
('Melissa', 'Lewis', 'commander')
>>>
>>> next(result)
('Rick', 'Martinez', 'pilot')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

As list:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>> data3 = ['botanist', 'commander', 'pilot']
>>>
>>> result = zip(data1, data2, data3)
>>> list(result)  
[('Mark', 'Watney', 'botanist'),
 ('Melissa', 'Lewis', 'commander'),
 ('Rick', 'Martinez', 'pilot')]

As dict:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis', 'Martinez']
>>> data3 = ['botanist', 'commander', 'pilot']
>>>
>>> result = zip(data1, data2, data3)
>>> dict(result)
Traceback (most recent call last):
ValueError: dictionary update sequence element #0 has length 3; 2 is required

9.5.9. Zip Shortest

  • zip() adjusts to the shortest

zip() is often used in cases where the iterables are assumed to be of equal length. If the length differs it will silently exit iterator.

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis']
>>>
>>> result = zip(data1, data2)
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis')]

9.5.10. Strict

  • zip(*iterables, strict=False)

  • Since Python 3.10: PEP 618 -- Add Optional Length-Checking To zip [1]

  • Source [2]

If the lengths of the iterables can differ, it's recommended to use the strict=True option. Without the strict=True argument, any bug that results in iterables of different lengths will be silenced, possibly manifesting as a hard-to-find issue in another part of the program. If the lengths are equal, the output is the same as regular zip(). However, zip() with strict=True checks that the lengths of the iterables are identical, raising a ValueError if they aren't.

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis']
>>>
>>> result = zip(data1, data2, strict=True)
>>> list(result)
Traceback (most recent call last):
ValueError: zip() argument 2 is shorter than argument 1

9.5.11. Zip Longest

  • from itertools import zip_longest

  • zip_longest(*iterables, [fillvalue=None])

SetUp:

>>> from itertools import zip_longest

Usage:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis']
>>>
>>> result = zip_longest(data1, data2)
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', None)]

Fill Value:

>>> data1 = ['Mark', 'Melissa', 'Rick']
>>> data2 = ['Watney', 'Lewis']
>>>
>>> result = zip_longest(data1, data2, fillvalue='n/a')
>>> list(result)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', 'n/a')]

9.5.12. Use Case - 1

>>> for user, address, order in zip(users, addresses, orders):  
...    print(f'Get {user} orders... {order}')

9.5.13. References

9.5.14. 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: Iterator Zip Dict
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

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

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

>>> from pprint import pprint
>>> pprint(result, width=72, sort_dicts=False)
{'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 = ...


# %% 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: Iterator Zip List[Dict]
# - Difficulty: easy
# - Lines: 2
# - Minutes: 5

# %% English
# 1. Define `result: list[dict]`:
# 2. Convert `DATA` from `list[tuple]` to `list[dict]`
#    - key - name from the header
#    - 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]`
#    - klucz - nazwa z nagłówka
#    - wartość - wartość numeryczna lub nazwa gatunku
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - list comprehension
# - `dict()`
# - `zip()`

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

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

>>> from pprint import pprint
>>> pprint(result, width=72, sort_dicts=False)
[{'sepal_length': 5.8,
  'sepal_width': 2.7,
  'petal_length': 5.1,
  'petal_width': 1.9,
  'species': 'virginica'},
 {'sepal_length': 5.1,
  'sepal_width': 3.5,
  'petal_length': 1.4,
  'petal_width': 0.2,
  'species': 'setosa'},
 {'sepal_length': 5.7,
  'sepal_width': 2.8,
  'petal_length': 4.1,
  'petal_width': 1.3,
  'species': 'versicolor'},
 {'sepal_length': 6.3,
  'sepal_width': 2.9,
  'petal_length': 5.6,
  'petal_width': 1.8,
  'species': 'virginica'},
 {'sepal_length': 6.4,
  'sepal_width': 3.2,
  'petal_length': 4.5,
  'petal_width': 1.5,
  'species': 'versicolor'},
 {'sepal_length': 4.7,
  'sepal_width': 3.2,
  'petal_length': 1.3,
  'petal_width': 0.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 = ...


# %% 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: Iterator Zip Dict
# - Difficulty: easy
# - Lines: 1
# - Minutes: 3

# %% English
# 1. Define `result: zip` with enumerated `DATA`
# 2. Recreate `enumerate()` behavior
# 3. Use only: `len()`, `range()`, `zip()`
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj `result: zip` z enumerowanym `DATA`
# 2. Odtwórz zachowanie `enumerate()`
# 3. Użyj tylko: `len()`, `range()`, `zip()`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `zip()`
# - `range()`
# - `len()`

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

>>> assert type(result) is zip
>>> next(result)
(0, 'January')
>>> next(result)
(1, 'February')
>>> next(result)
(2, 'March')
>>> next(result)
(3, 'April')
>>> next(result)
Traceback (most recent call last):
StopIteration
"""

DATA = ['January', 'February', 'March', 'April']

# Define `result: zip` with enumerated `DATA
# Recreate `enumerate()` behavior
# Use only: `len()`, `range()`, `zip()`
# type: zip
result = ...


# %% 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: Iterator Zip Impl
# - Difficulty: medium
# - Lines: 11
# - Minutes: 13

# %% English
# 1. Write own implementation of a built-in `zip()` function
# 2. Define function `myzip` with parameters:
#    - parameter `a: list | tuple`
#    - parameter `b: list | tuple`
#    - parameter `strict: bool`
# 3. Don't validate arguments and assume, that user will:
#    - always pass valid type of arguments
#    - iterable length will always be greater than 0
#    - user can only pass two iterables: `a`, `b`
# 4. Do not use built-in function `zip()`
# 5. Run doctests - all must succeed

# %% Polish
# 1. Zaimplementuj własne rozwiązanie wbudowanej funkcji `zip()`
# 2. Zdefiniuj funkcję `myzip` z parametrami:
#    - parametr `a: list | tuple`
#    - parametr `b: list | tuple`
#    - parametr `strict: bool`
# 3. Nie waliduj argumentów i przyjmij, że użytkownik:
#    - zawsze poda argumenty poprawnych typów
#    - długość iterable będzie większa od 0
#    - użytkownik może podać tylko dwie iterable: `a`, `b`
# 4. Nie używaj wbudowanej funkcji `zip()`
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `min()`
# - `len()`
# - `range()`
# - `list.append()`

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

>>> from inspect import isfunction
>>> assert isfunction(myzip)

>>> list(myzip(['a', 'b', 'c'], [1, 2, 3]))
[('a', 1), ('b', 2), ('c', 3)]

>>> dict(myzip(['a', 'b', 'c'], [1, 2, 3]))
{'a': 1, 'b': 2, 'c': 3}

>>> dict(myzip(['a', 'b', 'c'], [1, 2, 3, 4]))
{'a': 1, 'b': 2, 'c': 3}

>>> dict(myzip(['a', 'b', 'c'], [1, 2, 3], strict=True))
{'a': 1, 'b': 2, 'c': 3}

>>> dict(myzip(['a', 'b', 'c'], [1, 2, 3, 4], strict=True))
Traceback (most recent call last):
ValueError: zip() argument 2 is longer than argument 1

>>> dict(zip(['a', 'b', 'c', 'd'], [1, 2, 3], strict=True))
Traceback (most recent call last):
ValueError: zip() argument 2 is shorter than argument 1
"""

# Write own implementation of a built-in `zip()` function
# Define function `myrange` with parameters: `a`, `b`, `strict`
# type: Callable[[Iterable, Iterable, bool], list[tuple]]
def myzip(a, b, strict=False):
    ...