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

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

Pythonの `+ zip()`関数は、2つ以上の反復可能要素から要素を集約する反復子を作成します。 結果のイテレータを使用して、https://realpython.com/courses/dictionaries-python/[dictionaries]の作成など、一般的なプログラミングの問題を迅速かつ一貫して解決できます。 このチュートリアルでは、Pythonの ` zip()+`関数の背後にあるロジックと、それを使用して実際の問題を解決する方法を学びます。

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

  • Python 3とPython 2の両方で + zip()+ が機能する方法

  • Pythonの `+ zip()+`関数を*並列反復*に使用する方法

  • `+ zip()+`を使用してオンザフライで辞書を作成する方法*

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

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

`+ zip()`はhttps://docs.python.org/3/library/builtins.html[built-in namespace]で利用可能です。 ` builtins `を検査するために ` dir()`を使用すると、リストの最後に ` zip()+`が表示されます。

>>>

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

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

https://docs.python.org/3/library/functions.html#zip [公式ドキュメント]によると、Pythonの `+ zip()+`関数は次のように動作します。

_ タプルの反復子を返します。ここで、_i_番目のタプルには、各引数シーケンスまたは反復可能オブジェクトの_i_番目の要素が含まれます。 反復子は、最短の入力反復可能要素がなくなると停止します。 単一の反復可能な引数を使用すると、1タプルの反復子を返します。 引数なしで、空のイテレータを返します。 (https://docs.python.org/3/library/functions.html#zip [ソース]) _

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

Pythonで `+ zip()+`を使用する

Pythonの `+ zip()`関数は、 ` zip(* iterables)`として定義されています。 この関数は、https://docs.python.org/3/glossary.html#term-iterable [iterables]を引数として受け取り、 *iterator* を返します。 この反復子は、各反復可能要素からの要素を含む一連のタプルを生成します。 ` zip()+`は、https://realpython.com/read-write-files-python/[files]、https://realpython.com/python-lists-tuples/など、あらゆるタイプの反復可能オブジェクトを受け入れることができます[リスト、タプル]、https://realpython.com/python-dicts/[dictionaries]、https://realpython.com/python-sets/[sets]など。

`+ n +`引数を渡す

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

>>>

>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> zipped = zip(numbers, letters)
>>> zipped  # Holds an iterator object
<zip object at 0x7fa4831153c8>
>>> type(zipped)
<class 'zip'>
>>> list(zipped)
[(1, 'a'), (2, 'b'), (3, 'c')]

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

リスト、タプル、https://realpython.com/python-strings/[strings]などのシーケンスを使用している場合、イテラブルは左から右に評価されることが保証されています。 つまり、結果のタプルのリストは、 `+ [(numbers [0]、letters [0])、(numbers [1]、letters [1])、…​、(numbers [n]、letters [n])] + `。 ただし、他の種類の反復可能オブジェクト(https://realpython.com/python-sets/[sets]など)については、奇妙な結果が表示される場合があります。

>>>

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

この例では、「+ s1 」と「 s2 」は「 set 」オブジェクトであり、特定の順序で要素を保持しません。 つまり、 ` zip()`によって返されるタプルには、ランダムにペアになっている要素が含まれます。 Pythonの ` zip()+`関数を、セットのような順序付けされていないイテラブルとともに使用する場合、これは覚えておくべきことです。

引数を渡さない

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

>>>

>>> zipped = zip()
>>> zipped
<zip object at 0x7f196294a488>
>>> list(zipped)
[]

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

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

>>>

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

+ zipped +`でhttps://docs.python.org/3/library/functions.html#next [+ next()`]を呼び出すと、Pythonは次のアイテムを取得しようとします。 ただし、 ` zipped `は空のイテレータを保持するため、取り出すものは何もないため、Pythonは ` StopIteration +`例外を発生させます。

1つの引数を渡す

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

>>>

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

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

ご覧のとおり、必要な数の入力イテラブルでPythonの `+ zip()+`関数を呼び出すことができます。 結果のタプルの長さは、引数として渡すイテラブルの数と常に等しくなります。 次に、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 +`は最初の(そして最短の)https://realpython.com/python-range/[+ range()`]オブジェクトの長さなので、 ` zip()`は次のリストを出力します5つのタプル。 2番目の ` range()`オブジェクトには、まだ95個の一致しない要素があります。 これらはすべて、ペアを完成させる最初の ` range()`オブジェクトの要素がないため、 ` zip()+`によって無視されます。

末尾の値または一致しない値が重要な場合は、 +の代わりにhttps://docs.python.org/3/library/itertools.html#itertools.zip_longest [ + itertools.zip_longest()+ `]を使用できますzip()+ `。 この関数では、欠損値は、 `+ 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()」を使用して、「 letters 」、「 numbers 」、および「 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)
<type 'list'>
>>> zipped = zip()  # Create an empty list
>>> zipped
[]

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

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

>>>

>>> # Python 3
>>> zipped = zip(range(3), 'ABCD')
>>> zipped  # Hold an iterator
<zip object at 0x7f456ccacbc8>
>>> type(zipped)
<class 'zip'>
>>> list(zipped)
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> zipped = zip()  # Create an empty iterator
>>> zipped
<zip object at 0x7f456cc93ac8>
>>> next(zipped)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    next(zipped)
StopIteration

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

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

>>>

>>> # Python 2
>>> from itertools import izip
>>> zipped = izip(range(3), 'ABCD')
>>> zipped
<itertools.izip object at 0x7f3614b3fdd0>
>>> 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

ここで、 + itertools +`で `+ izip()+`を使用できる場合、Python 2にいることがわかり、 `+ izip()+`はエイリアス `+ zip +`を使用してインポートされます。 そうしないと、プログラムで `+ ImportError +`が発生し、Python 3にいることがわかります。 (https://realpython.com/lessons/pass-statement/[+ pass +` statement]は単なるプレースホルダーです。)

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

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

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

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

リストの並行走査

Pythonの + zip()+`関数を使用すると、2つ以上の反復可能オブジェクトを並列に反復処理できます。 `+ zip()+`はタプルを生成するため、これらをhttps://realpython.com/courses/python-for-loop/[+ 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()+`によって返される一連のタプルを反復処理し、要素を `+ l +`と `+ n +`にアンパックします。 `+ zip()+、 `+ for +`ループ、およびhttps://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences[tuple unpacking]を組み合わせると、一度に2つ以上のイテラブルをトラバースするための便利なhttps://realpython.com/courses/idiomatic-python-101/[Pythonic]イディオム。

単一の `+ 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つのアイテムを生成するイテレータを作成して返すために、3つの反復可能オブジェクトで `+ zip()`を使用します。 これにより、3つの反復可能要素すべてを一度に繰り返すことができます。 Pythonの ` zip()+`関数で使用できる反復可能オブジェクトの数に制限はありません。

注意: Pythonの `+ for +`ループをさらに詳しく知りたい場合は、https://realpython.com/python-for-loop/[Python“ for” Loops(Definite Iteration)]を確認してください。

辞書を並行して走査する

Python 3.6以降では、辞書はhttps://docs.python.org/3/whatsnew/3.6.html#whatsnew36-compactdict[ordered collections]です。つまり、要素は導入時と同じ順序で保持されます。 この機能を利用する場合、Pythonの `+ zip()+`関数を使用して、安全で一貫した方法で複数の辞書を反復処理できます。

>>>

>>> 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_one `と ` dict_two `を並行して繰り返します。 この場合、 ` zip()+`は両方の辞書からの項目でタプルを生成します。 その後、各タプルをアンパックして、両方の辞書のアイテムに同時にアクセスできます。

*注:*辞書の反復をさらに詳しく知りたい場合は、https://realpython.com/iterate-through-dictionary-python/[Pythonで辞書を反復処理する方法]を確認してください。

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

シーケンスの解凍

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

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

タプルのリストがあり、各タプルの要素を独立したシーケンスに分割するとします。 これを行うには、https://realpython.com/python-kwargs-and-args/#unpacking-with-the-asterisk-operators [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つの異なるリスト( ` numbers `と ` letters +`)を作成します。

並列ソート

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

>>>

>>> 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()`と組み合わせてソートします。 ` data1 `が ` letters `でソートされ、 ` data2 `が ` numbers +`でソートされていることに注意してください。

同様の結果を達成するために、 `+ 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 +`によってアイテムをすべて一度にソートします。 2つの関数呼び出し( `+ zip()+`と `+ sorted()+)のみが必要なため、このアプローチは少し速くなります。

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

ペアで計算する

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

Element/Month January February March

Total Sales

52,000.00

51,000.00

48,000.00

Production Cost

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のhttps://realpython.com/python-dicts/[dictionaries]は非常に便利なデータ構造です。 場合によっては、密接に関連する2つの異なるシーケンスから辞書を作成する必要があります。 これを実現する便利な方法は、 `+ dict()`と ` zip()+`を一緒に使用することです。 たとえば、フォームまたはデータベースから人のデータを取得したとします。 これで、次のデータのリストができました。

>>>

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

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

>>>

>>> 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()`関数を使用して作成したキーと値のタプルで辞書を更新します。 この手法を使用すると、 ` job +`の値を簡単に上書きできます。

結論

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

今あなたはできる:

  • * Python 3とPython 2の両方で `+ zip()+`関数を使用します*

  • *複数のイテラブルをループ*し、アイテムに対して異なるアクションを並行して実行します

  • * 2つの入力反復可能要素を一緒に圧縮することにより、辞書をその場で作成および更新します*

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