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

10.5.1. Problem

Using while loop:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> 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 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> result = []
>>> count = min(len(data1), len(data2))
>>> for i in range(count):
...     a = data1[i]
...     b = data2[i]
...     result.append((a,b))

10.5.2. Solution

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> result = zip(data1, data2)

10.5.3. Lazy Evaluation

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> result = zip(data1, data2)
>>>
>>> next(result)
('Alice', 'Apricot')
>>>
>>> next(result)
('Bob', 'Blackthorn')
>>>
>>> next(result)
('Carol', 'Corn')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

10.5.4. Iteration

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> for result in zip(data1, data2):
...     print(result)
('Alice', 'Apricot')
('Bob', 'Blackthorn')
('Carol', 'Corn')

10.5.5. Unpacking

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> for firstname, lastname in zip(data1, data2):
...     print(f'{firstname=}, {lastname=}')
firstname='Alice', lastname='Apricot'
firstname='Bob', lastname='Blackthorn'
firstname='Carol', lastname='Corn'

10.5.6. As List

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> result = zip(data1, data2)
>>> list(result)
[('Alice', 'Apricot'), ('Bob', 'Blackthorn'), ('Carol', 'Corn')]

10.5.7. As Dict

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>>
>>> result = zip(data1, data2)
>>> dict(result)
{'Alice': 'Apricot', 'Bob': 'Blackthorn', 'Carol': 'Corn'}

10.5.8. Many Iterables

Lazy Evaluation:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>> data3 = ['alice@example.com', 'bob@example.com', 'carol@example.com']
>>>
>>> result = zip(data1, data2, data3)
>>>
>>> next(result)
('Alice', 'Apricot', 'alice@example.com')
>>>
>>> next(result)
('Bob', 'Blackthorn', 'bob@example.com')
>>>
>>> next(result)
('Carol', 'Corn', 'carol@example.com')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

As list:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>> data3 = ['alice@example.com', 'bob@example.com', 'carol@example.com']
>>>
>>> result = zip(data1, data2, data3)
>>> list(result)
[('Alice', 'Apricot', 'alice@example.com'),
 ('Bob', 'Blackthorn', 'bob@example.com'),
 ('Carol', 'Corn', 'carol@example.com')]

As dict:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn', 'Corn']
>>> data3 = ['alice@example.com', 'bob@example.com', 'carol@example.com']
>>>
>>> result = zip(data1, data2, data3)
>>> dict(result)
Traceback (most recent call last):
ValueError: dictionary update sequence element #0 has length 3; 2 is required

10.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 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn']
>>>
>>> result = zip(data1, data2)
>>> list(result)
[('Alice', 'Apricot'), ('Bob', 'Blackthorn')]

10.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 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn']
>>>
>>> result = zip(data1, data2, strict=True)
>>> list(result)
Traceback (most recent call last):
ValueError: zip() argument 2 is shorter than argument 1

10.5.11. Zip Longest

  • from itertools import zip_longest

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

SetUp:

>>> from itertools import zip_longest

Usage:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn']
>>>
>>> result = zip_longest(data1, data2)
>>> list(result)
[('Alice', 'Apricot'), ('Bob', 'Blackthorn'), ('Carol', None)]

Fill Value:

>>> data1 = ['Alice', 'Bob', 'Carol']
>>> data2 = ['Apricot', 'Blackthorn']
>>>
>>> result = zip_longest(data1, data2, fillvalue='n/a')
>>> list(result)
[('Alice', 'Apricot'), ('Bob', 'Blackthorn'), ('Carol', 'n/a')]

10.5.12. Use Case - 1

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

10.5.13. References

10.5.14. Assignments

# %% About
# - Name: Iterator Zip Dict
# - 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. Zip `HEADER` and `ROW` to `dict`
# 2. Define `result: dict` with the result
# 3. Use `zip()`
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zzipuj `HEADER` i `ROW` do `dict`
# 2. Zdefiniuj `result: dict` z wynikiem
# 3. Użyj `zip()`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Example
# >>> result
# {'firstname': 'Alice', 'lastname': 'Apricot', 'age': 30}

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

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

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

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

>>> result
{'firstname': 'Alice', 'lastname': 'Apricot', 'age': 30}
"""

# %% 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: dict[str,str|int]

# %% Data
HEADER = ('firstname', 'lastname', 'age')
ROW = ('Alice', 'Apricot', 30)

# %% Result
result = ...

# %% About
# - Name: Iterator Zip ToListDict
# - Difficulty: easy
# - Lines: 2
# - 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. Convert `DATA` from  `list[tuple]` to `list[dict]`
# 2. First row has column names (keys in result `dict`)
# 2. Define variable `result` with the result
# 3. Run doctests - all must succeed

# %% Polish
# 1. Przekonwertuj `DATA` z `list[tuple]` do `list[dict]`
# 2. Pierwszy wiersz ma nazwy kolumn (klucze w wynikowym `dict`)
# 3. Zdefiniuj zmienną `result` z wynikiem
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Example
# >>> result
# [{'firstname': 'Alice', 'lastname': 'Apricot', 'age': 30},
#  {'firstname': 'Bob', 'lastname': 'Blackthorn', 'age': 31},
#  {'firstname': 'Carol', 'lastname': 'Corn', 'age': 32},
#  {'firstname': 'Dave', 'lastname': 'Durian', 'age': 33},
#  {'firstname': 'Eve', 'lastname': 'Elderberry', 'age': 34},
#  {'firstname': 'Mallory', 'lastname': 'Melon', 'age': 15}]

# %% Why
# - Convert data from `list[tuple]` to `list[dict]`
# - `list[tuple]` is used to represent CSV data
# - `list[tuple]` is used to represent database rows
# - `list[dict]` is used to represent JSON data
# - CSV is the most popular format in data science

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

>>> assert result is not Ellipsis, \
'Variable `result` has an invalid value; assign result of your program to it.'
>>> result = list(result)
>>> assert type(result) is list, \
'Variable `result` has an invalid type; expected: `list`.'

>>> assert len(result) > 0, \
'Variable `result` length is zero; it should contain items.'

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

>>> from pprint import pprint
>>> pprint(result, sort_dicts=False)
[{'firstname': 'Alice', 'lastname': 'Apricot', 'age': 30},
 {'firstname': 'Bob', 'lastname': 'Blackthorn', 'age': 31},
 {'firstname': 'Carol', 'lastname': 'Corn', 'age': 32},
 {'firstname': 'Dave', 'lastname': 'Durian', 'age': 33},
 {'firstname': 'Eve', 'lastname': 'Elderberry', 'age': 34},
 {'firstname': 'Mallory', 'lastname': 'Melon', 'age': 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
result: list[dict[str,str|int]]

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


# %% Result
result = ...

# %% About
# - Name: Iterator Zip Impl
# - Difficulty: medium
# - Lines: 11
# - Minutes: 13

# %% 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. Write own implementation of a built-in `zip()` function
# 2. Define function `myzip` with parameters:
#    - `a: list|tuple` - first iterable
#    - `b: list|tuple` - second iterable
#    - `strict: bool` - flag, default False
# 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. If `strict` is True and `b` is longer than `b` then raise
#    `ValueError` with message: 'myzip() argument 2 is longer than argument 1'
# 5. If `strict` is True and `b` is shorter than `a` then raise
#    `ValueError` with message: 'myzip() argument 2 is shorter than argument 1'
# 6. Do not use built-in function `zip()`
# 7. Use `yield` keyword
# 8. Run doctests - all must succeed

# %% Polish
# 1. Zaimplementuj własne rozwiązanie wbudowanej funkcji `zip()`
# 2. Zdefiniuj funkcję `myzip` z parametrami:
#    - `a: list|tuple` - pierwsza iterable
#    - `b: list|tuple` - drugie iterable
#    - `strict: bool` - flaga, domyślnie False
# 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. Jeżeli `strict` jest `True` oraz `b` jest dłuższe niż `a` to podnieś
#    `ValueError` z komunikatem: 'myzip() argument 2 is longer than argument 1'
# 5. Jeżeli `strict` jest `True` oraz `b` jest krótsze niż `a` to podnieś
#    `ValueError` z komunikatem: 'myzip() argument 2 is shorter than argument 1'
# 6. Nie używaj wbudowanej funkcji `zip()`
# 7. Użyj słowa kluczowego `yield`
# 8. Uruchom doctesty - wszystkie muszą się powieść

# %% Example
# >>> 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]))
# {'a': 1, 'b': 2, 'c': 3}
#
# >>> dict(myzip(['a', 'b', 'c', 'd'], [1, 2, 3]))
# {'a': 1, 'b': 2, 'c': 3}
#
# >>> dict(myzip(['a', 'b', 'c'], [1, 2, 3, 4], strict=True))
# Traceback (most recent call last):
# ValueError: myzip() argument 2 is longer than argument 1
#
# >>> dict(myzip(['a', 'b', 'c', 'd'], [1, 2, 3], strict=True))
# Traceback (most recent call last):
# ValueError: myzip() argument 2 is shorter than argument 1

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

# %% Doctests
"""
>>> 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]))
{'a': 1, 'b': 2, 'c': 3}

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

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

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

# %% 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
from typing import Callable
myzip: Callable[[tuple|list, tuple|list, bool], list[tuple]]

# %% Data

# %% Result
def myzip(a, b, strict=False):
    ...