並列反復のためのPython zip()関数の使用

並列反復のためのPython zip()関数の使用

Pythonのzip()関数は、2つ以上の反復可能オブジェクトから要素を集約する反復子を作成します。 結果のイテレータを使用して、dictionariesの作成など、一般的なプログラミングの問題を迅速かつ一貫して解決できます。 このチュートリアルでは、Pythonzip()関数の背後にあるロジックと、それを使用して実際の問題を解決する方法について説明します。

このチュートリアルの終わりまでに、次のことを学びます。

  • Python3とPython2の両方でzip()がどのように機能するか

  • parallel iterationにPythonzip()関数を使用する方法

  • zip()を使用してオンザフライでcreate dictionariesを実行する方法

Free Bonus:5 Thoughts On Python Masteryは、Python開発者向けの無料コースで、Pythonスキルを次のレベルに引き上げるために必要なロードマップと考え方を示しています。

Pythonのzip()関数を理解する

zip()built-in namespaceで使用できます。 dir()を使用して__builtins__を検査する場合、リストの最後にzip()が表示されます。

>>>

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', ..., 'zip']

'zip'が使用可能なオブジェクトのリストの最後のエントリであることがわかります。

official documentationによると、Pythonのzip()関数は次のように動作します。

タプルのイテレータを返します。ここで、i番目のタプルには、各引数シーケンスまたは反復可能オブジェクトからのi番目の要素が含まれます。 反復子は、最短の入力反復可能要素がなくなると停止します。 単一の反復可能な引数を使用すると、1タプルの反復子を返します。 引数なしで、空のイテレータを返します。 (Source

この定義は、チュートリアルの残りの部分で展開します。 コード例を使用すると、Pythonのzip操作が、バッグやジーンズの物理的なジッパーのように機能することがわかります。 ジッパーの両側の噛み合う歯のペアは、開口部を閉じるために一緒に引っ張られます。 実際、この視覚的なアナロジーは、zip()を理解するのに最適です。これは、関数が物理的なジッパーにちなんで名付けられたためです。

Pythonでzip()を使用する

Pythonのzip()関数は、zip(*iterables)として定義されています。 この関数は、iterablesを引数として受け取り、iteratorを返します。 この反復子は、各反復可能要素からの要素を含む一連のタプルを生成します。 zip()は、fileslists, tuplesdictionariessetsなどの任意のタイプの反復可能オブジェクトを受け入れることができます。

n引数を渡す

n引数でzip()を使用する場合、関数は長さnのタプルを生成するイテレーターを返します。 この動作を確認するには、次のコードブロックをご覧ください。

>>>

>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> zipped = zip(numbers, letters)
>>> zipped  # Holds an iterator object

>>> type(zipped)

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

ここでは、zip(numbers, letters)を使用して、(x, y)の形式のタプルを生成するイテレーターを作成します。 この場合、xの値はnumbersから取得され、yの値はlettersから取得されます。 Pythonzip()関数がイテレータを返す方法に注目してください。 最終的なリストオブジェクトを取得するには、list()を使用してイテレータを使用する必要があります。

リスト、タプル、stringsなどのシーケンスを使用している場合、反復可能オブジェクトは左から右に評価されることが保証されています。 これは、結果のタプルのリストが[(numbers[0], letters[0]), (numbers[1], letters[1]),..., (numbers[n], letters[n])]の形式になることを意味します。 ただし、他のタイプの反復可能オブジェクト(setsなど)の場合、奇妙な結果が表示される場合があります。

>>>

>>> s1 = {2, 3, 1}
>>> s2 = {'b', 'a', 'c'}
>>> list(zip(s1, s2))
[(1, 'a'), (2, 'c'), (3, 'b')]

この例では、s1s2setオブジェクトであり、要素を特定の順序で保持していません。 これは、zip()によって返されるタプルには、ランダムにペアになる要素があることを意味します。 セットのような順序付けられていない反復可能オブジェクトでPythonzip()関数を使用する場合は、これを覚えておく必要があります。

引数を渡さない

引数なしでzip()を呼び出すこともできます。 この場合、空のイテレーターを取得するだけです:

>>>

>>> zipped = zip()
>>> zipped

>>> list(zipped)
[]

ここでは、引数なしでzip()を呼び出すため、zipped変数は空のイテレーターを保持します。 list()でイテレータを使用すると、空のリストも表示されます。

空のイテレータに要素を直接生成させることもできます。 この場合、StopIterationexceptionを取得します。

>>>

>>> zipped = zip()
>>> next(zipped)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

zippednext()を呼び出すと、Pythonは次のアイテムを取得しようとします。 ただし、zippedは空のイテレータを保持しているため、引き出すものがないため、PythonはStopIteration例外を発生させます。

1つの引数を渡す

Pythonのzip()関数も、引数を1つだけ取ることができます。 結果は、一連の1項目タプルを生成する反復子になります。

>>>

>>> a = [1, 2, 3]
>>> zipped = zip(a)
>>> list(zipped)
[(1,), (2,), (3,)]

これはそれほど便利ではないかもしれませんが、それでも動作します。 おそらく、zip()のこの動作のいくつかのユースケースを見つけることができます!

ご覧のとおり、必要な数の入力反復可能オブジェクトを使用してPythonzip()関数を呼び出すことができます。 結果のタプルの長さは、引数として渡すイテラブルの数と常に等しくなります。 次に、3つの反復可能要素の例を示します。

>>>

>>> integers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> floats = [4.0, 5.0, 6.0]
>>> zipped = zip(integers, letters, floats)  # Three input iterables
>>> list(zipped)
[(1, 'a', 4.0), (2, 'b', 5.0), (3, 'c', 6.0)]

ここでは、Pythonのzip()関数を3つの反復可能オブジェクトで呼び出すため、結果のタプルにはそれぞれ3つの要素があります。

等しくない長さの引数を渡す

Pythonのzip()関数を使用するときは、反復可能オブジェクトの長さに注意を払うことが重要です。 引数として渡す反復可能要素が同じ長さではない可能性があります。

これらの場合、zip()が出力する要素の数は、shortest反復可能の長さに等しくなります。 以下に示すように、これ以上反復可能な要素の残りの要素は、zip()によって完全に無視されます。

>>>

>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

5は最初の(そして最短の)range()オブジェクトの長さであるため、zip()は5つのタプルのリストを出力します。 2番目のrange()オブジェクトからの95の一致しない要素がまだあります。 ペアを完成させる最初のrange()オブジェクトからの要素がこれ以上ないため、これらはすべてzip()によって無視されます。

末尾の値または一致しない値が重要な場合は、zip()の代わりにitertools.zip_longest()を使用できます。 この関数を使用すると、欠落している値は、fillvalue引数に渡したものに置き換えられます(デフォルトはNone)。 反復は、最長の反復可能要素がなくなるまで続きます。

>>>

>>> from itertools import zip_longest
>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> longest = range(5)
>>> zipped = zip_longest(numbers, letters, longest, fillvalue='?')
>>> list(zipped)
[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2), ('?', '?', 3), ('?', '?', 4)]

ここでは、itertools.zip_longest()を使用して、lettersnumbers、およびlongestの要素を持つ5つのタプルを生成します。 反復は、longestが使い果たされたときにのみ停止します。 numbersおよびlettersから欠落している要素は、fillvalueで指定した疑問符?で埋められます。

Python 3と2でのzip()の比較

Pythonのzip()関数は、両方のバージョンの言語で動作が異なります。 Python 2では、zip()listのタプルを返します。 結果のlistは、反復可能な最短の入力の長さに切り捨てられます。 引数なしでzip()を呼び出すと、代わりに空のlistが返されます。

>>>

>>> # Python 2
>>> zipped = zip(range(3), 'ABCD')
>>> zipped  # Hold a list object
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> type(zipped)

>>> zipped = zip()  # Create an empty list
>>> zipped
[]

この場合、Pythonzip()関数を呼び出すと、値Cで切り捨てられたタプルのリストが返されます。 引数なしでzip()を呼び出すと、空のlistが得られます。

ただし、Python 3では、zip()iteratorを返します。 このオブジェクトは、要求に応じてタプルを生成し、一度だけ通過できます。 反復可能な最短の入力が使い果たされると、反復はStopIteration例外で終了します。 zip()に引数を指定しない場合、関数は空のイテレータを返します。

>>>

>>> # Python 3
>>> zipped = zip(range(3), 'ABCD')
>>> zipped  # Hold an iterator

>>> type(zipped)

>>> list(zipped)
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> zipped = zip()  # Create an empty iterator
>>> zipped

>>> next(zipped)
Traceback (most recent call last):
  File "", line 1, in 
    next(zipped)
StopIteration

ここで、zip()を呼び出すと、イテレータが返されます。 最初の反復はCで切り捨てられ、2番目の反復はStopIteration例外になります。 Python 3では、返されたイテレータをlist()の呼び出しでラップすることにより、zip()のPython2の動作をエミュレートすることもできます。 これはイテレータを介して実行され、タプルのリストを返します。

Python 2を定期的に使用している場合は、長い入力反復可能オブジェクトでzip()を使用すると、意図せずに大量のメモリを消費する可能性があることに注意してください。 このような状況では、代わりにitertools.izip(*iterables)の使用を検討してください。 この関数は、各反復可能要素から要素を集約する反復子を作成します。 Python 3のzip()と同じ効果が得られます。

>>>

>>> # Python 2
>>> from itertools import izip
>>> zipped = izip(range(3), 'ABCD')
>>> zipped

>>> list(zipped)
[(0, 'A'), (1, 'B'), (2, 'C')]

この例では、itertools.izip()を呼び出してイテレータを作成します。 返されたイテレータをlist()で使用すると、Python 3でzip()を使用しているかのように、タプルのリストが表示されます。 反復は、反復可能な最短入力がなくなると停止します。

Python 2とPython 3の両方で同じように動作するコードを書く必要がある場合は、次のようなトリックを使用できます。

try:
    from itertools import izip as zip
except ImportError:
    pass

ここで、izip()itertoolsで使用できる場合は、Python 2を使用していることがわかり、izip()はエイリアスzipを使用してインポートされます。 それ以外の場合、プログラムはImportErrorを生成し、Python3を使用していることがわかります。 (ここでのpass statementは単なるプレースホルダーです。)

このトリックを使用すると、コード全体でPythonzip()関数を安全に使用できます。 実行すると、プログラムは自動的に正しいバージョンを選択して使用します。

これまで、Pythonのzip()関数がどのように機能するかを説明し、Pythonの最も重要な機能のいくつかについて学習しました。 さあ、袖をまくり、実際の例をコーディングしてみましょう。

複数のイテラブルのループ

複数の反復可能オブジェクトをループすることは、Pythonのzip()関数の最も一般的なユースケースの1つです。 複数のリスト、タプル、またはその他のシーケンスを反復処理する必要がある場合は、zip()にフォールバックする可能性があります。 このセクションでは、zip()を使用して、複数の反復可能オブジェクトを同時に反復処理する方法を示します。

リストの並行走査

Pythonのzip()関数を使用すると、2つ以上の反復可能オブジェクトを並行して反復できます。 zip()はタプルを生成するため、for loopのヘッダーでこれらをアンパックできます。

>>>

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> for l, n in zip(letters, numbers):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...
Letter: a
Number: 0
Letter: b
Number: 1
Letter: c
Number: 2

ここでは、zip()によって返される一連のタプルを反復処理し、要素をlnに解凍します。 zip()forループ、およびtuple unpackingを組み合わせると、2つ以上の反復可能オブジェクトを一度にトラバースするための便利なPythonicイディオムを取得できます。

1つのforループで3つ以上の反復可能オブジェクトを反復処理することもできます。 3つの入力反復可能要素がある次の例を考えてみましょう。

>>>

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> operators = ['*', '/', '+']
>>> for l, n, o in zip(letters, numbers, operators):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...     print(f'Operator: {o}')
...
Letter: a
Number: 0
Operator: *
Letter: b
Number: 1
Operator: /
Letter: c
Number: 2
Operator: +

この例では、3つのイテレータでzip()を使用して、3項目のタプルを生成するイテレータを作成して返します。 これにより、3つの反復可能要素すべてを一度に繰り返すことができます。 Pythonのzip()関数で使用できる反復可能オブジェクトの数に制限はありません。

Note: Pythonforループをさらに深く掘り下げたい場合は、Python “for” Loops (Definite Iteration)を確認してください。

辞書を並行して走査する

Python 3.6以降では、辞書はordered collectionsです。つまり、辞書は、導入されたときと同じ順序で要素を保持します。 この機能を利用する場合は、Pythonzip()関数を使用して、安全で一貫性のある方法で複数の辞書を反復処理できます。

>>>

>>> dict_one = {'name': 'John', 'last_name': 'Doe', 'job': 'Python Consultant'}
>>> dict_two = {'name': 'Jane', 'last_name': 'Doe', 'job': 'Community Manager'}
>>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
...     print(k1, '->', v1)
...     print(k2, '->', v2)
...
name -> John
name -> Jane
last_name -> Doe
last_name -> Doe
job -> Python Consultant
job -> Community Manager

ここでは、dict_onedict_twoを並行して繰り返します。 この場合、zip()は、両方のディクショナリからのアイテムを使用してタプルを生成します。 その後、各タプルをアンパックして、両方の辞書のアイテムに同時にアクセスできます。

Note:辞書の反復をさらに深く掘り下げたい場合は、How to Iterate Through a Dictionary in Pythonを確認してください。

上記の例では、左から右への評価順序が保証されていることに注意してください。 Pythonのzip()関数を使用して、セットを並列に反復することもできます。 ただし、Python 3.6の辞書とは異なり、セットdon’tは要素を順番に保持することを考慮する必要があります。 この詳細を忘れると、プログラムの最終的な結果が期待したものまたは期待したものにならない可能性があります。

シーケンスの解凍

新しいPythonistaのフォーラムで頻繁に出てくる質問があります。「zip()関数があるのに、なぜ反対のunzip()関数がないのですか?」

Pythonにunzip()関数がない理由は、zip()の反対が…まあ、zip()だからです。 Pythonのzip()関数が実際のジッパーのように機能することを覚えていますか? これまでの例は、Pythonが物事を圧縮する方法を示しました。 それでは、Pythonオブジェクトをどのように解凍しますか?

タプルのリストがあり、各タプルの要素を独立したシーケンスに分割するとします。 これを行うには、次のように、unpacking operator *と一緒にzip()を使用できます。

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
>>> numbers, letters = zip(*pairs)
>>> numbers
(1, 2, 3, 4)
>>> letters
('a', 'b', 'c', 'd')

ここでは、ある種の混合データを含むタプルのlistがあります。 次に、解凍​​演算子*を使用してデータを解凍し、2つの異なるリスト(numbersletters)を作成します。

並列ソート

Sortingは、プログラミングの一般的な操作です。 2つのリストを組み合わせて、同時に並べ替えたいとします。 これを行うには、次のようにzip().sort()と一緒に使用できます。

>>>

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data1 = list(zip(letters, numbers))
>>> data1
[('b', 2), ('a', 4), ('d', 3), ('c', 1)]
>>> data1.sort()  # Sort by letters
>>> data1
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
>>> data2 = list(zip(numbers, letters))
>>> data2
[(2, 'b'), (4, 'a'), (3, 'd'), (1, 'c')]
>>> data2.sort()  # Sort by numbers
>>> data2
[(1, 'c'), (2, 'b'), (3, 'd'), (4, 'a')]

この例では、最初に2つのリストをzip()と組み合わせて、それらを並べ替えます。 data1lettersでソートされ、data2numbersでソートされていることに注目してください。

sorted()zip()を一緒に使用して、同様の結果を得ることができます。

>>>

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data = sorted(zip(letters, numbers))  # Sort by letters
>>> data
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]

この場合、sorted()zip()によって生成されたイテレータを実行し、アイテムをlettersで並べ替えます。 このアプローチは、zip()sorted()の2つの関数呼び出しのみが必要になるため、少し速くなる可能性があります。

sorted()を使用すると、より一般的なコードも記述できます。 これにより、リストだけでなく、あらゆる種類のシーケンスをソートできます。

ペアで計算する

Pythonのzip()関数を使用して、簡単な計算を行うことができます。 スプレッドシートに次のデータがあるとします:

Element/Month 1月 2月 行進

総売上

52,000.00

51,000.00

48,000.00

生産コスト

46,800.00

45,900.00

43,200.00

このデータを使用して、毎月の利益を計算します。 zip()は、計算を行うための高速な方法を提供します。

>>>

>>> total_sales = [52000.00, 51000.00, 48000.00]
>>> prod_cost = [46800.00, 45900.00, 43200.00]
>>> for sales, costs in zip(total_sales, prod_cost):
...     profit = sales - costs
...     print(f'Total profit: {profit}')
...
Total profit: 5200.0
Total profit: 5100.0
Total profit: 4800.0

ここでは、salesからcostsを引くことにより、各月の利益を計算します。 Pythonのzip()関数は、適切なデータのペアを組み合わせて計算を行います。 このロジックを一般化して、zip()によって返されるペアを使用してあらゆる種類の複雑な計算を行うことができます。

辞書の作成

Pythonのdictionariesは、非常に便利なデータ構造です。 場合によっては、密接に関連する2つの異なるシーケンスから辞書を作成する必要があります。 これを実現する便利な方法は、dict()zip()を一緒に使用することです。 たとえば、フォームまたはデータベースから人のデータを取得したとします。 これで、次のデータのリストができました。

>>>

>>> fields = ['name', 'last_name', 'age', 'job']
>>> values = ['John', 'Doe', '45', 'Python Developer']

このデータを使用して、さらに処理するための辞書を作成する必要があります。 この場合、次のようにzip()と一緒にdict()を使用できます。

>>>

>>> a_dict = dict(zip(fields, values))
>>> a_dict
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Developer'}

ここでは、2つのリストを組み合わせた辞書を作成します。 zip(fields, values)は、2項目のタプルを生成するイテレーターを返します。 そのイテレータでdict()を呼び出すと、必要な辞書を作成することになります。 fieldsの要素は辞書のキーになり、valuesの要素は辞書の値を表します。

zip()dict.update()を組み合わせて、既存の辞書を更新することもできます。 Johnが仕事を変更し、辞書を更新する必要があるとします。 次のようなことができます。

>>>

>>> new_job = ['Python Consultant']
>>> field = ['job']
>>> a_dict.update(zip(field, new_job))
>>> a_dict
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Consultant'}

ここで、dict.update()は、Pythonのzip()関数を使用して作成したKey-Valueタプルで辞書を更新します。 この手法を使用すると、jobの値を簡単に上書きできます。

結論

このチュートリアルでは、Pythonのzip()関数の使用方法を学習しました。 zip()は、入力として複数の反復可能オブジェクトを受け取ることができます。 各引数からペアの要素を持つタプルを生成できる反復子を返します。 結果のイテレータは、単一のループで複数のイテラブルを処理し、それらのアイテムに対していくつかのアクションを同時に実行する必要がある場合に非常に役立ちます。

今あなたはできる:

  • Python3とPython2の両方でUse the zip() function

  • Loop over multiple iterablesを実行し、アイテムに対してさまざまなアクションを並行して実行します

  • 2つの入力反復可能オブジェクトを一緒に圧縮することによるオンザフライでのCreate and update dictionaries

また、Pythonのzip()関数を使用して独自のソリューションを実装するための開始点として使用できるいくつかの例をコーディングしました。 zip()を詳しく調べながら、これらの例を自由に変更してください。