Pythonのzipの使い方: 複数のイテラブルからイテレーターを作る
- 作成日: 2020-09-07
- 更新日: 2024-01-02
- カテゴリ: Python
Pythonのzip関数
Pythonには組み込み関数のzip
があります。
この関数は複数のリストなどのイテラブルなオブジェクトから、1つのzip
オブジェクトを作るときに使われます。
zip
オブジェクトはfor文などで回すと、引数の各リストなどの各要素を1つのタプルにしてくれます。
たとえば複数のリストを1つのfor
文で回したい時などに有用です。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
for a, b in zip(lis1, lis2):
print(a, b)
実行結果。
1 cat
2 dog
3 bird
zipオブジェクトの作成
組み込み関数のzip()
は複数のイテラブルなオブジェクト(リストやタプル、辞書など)から1つのzip
オブジェクトを作成します。
zip(*iterables, strict=False)
zip
オブジェクトは遅延評価されるオブジェクトです。
実際にfor文で回されたりnext()
でイテレーターを進めたりしない限り、引数iterables
の要素を処理することはありません。
iterables
は複数のイテレーション可能なオブジェクトを指します。
イテレーション可能なオブジェクトとはlist
やtuple
やset
やdict
などです。
これらの複数のオブジェクトを混合してzip()
に渡すことができます。
z = zip([1, 2], (3, 4), {5, 6}, {'a': 1, 'b': 2})
print(z)
# <zip object at 0x000001F060102600>
print(list(z))
# [(1, 3, 5, 'a'), (2, 4, 6, 'b')]
zip()
のstrict
キーワード引数はバージョン3.10から追加されました。
strict
をTrue
にするとzip
オブジェクトをfor文で回したときに特定の条件でValueError
例外が送出されるようになります。
try:
for item in zip([1, 2], (3, 4, 5), strict=True):
print(item)
except ValueError as e:
print(e)
# (1, 3)
# (2, 4)
# zip() argument 2 is longer than argument 1
特定の条件とはzip()
に渡すiterables
の各オブジェクトの要素数が異なる時です。
上記の例では[1, 2]
と(3, 4, 5)
を渡していますが、前者は要素数が2で後者は要素数が3になっているのでエラーになります。
このエラーですがfor文などでイテレーターを進めると現れますので、単純にzip object
を作っただけではエラーは現れません。
zip([1, 2], (3, 4, 5), strict=True)
zip
オブジェクトはリストやタプルに変換可能なほか、next
にも渡せるしfor
文で回すこともできます。
CPythonのzip_new関数の実装
CPython3.13ではzip()
で呼ばれる関数はcpython/Python/bltinmodule.c
のzip_new()
関数です。
この関数ではまず最初にキーワード引数のチェックを行っています。
このキーワード引数のチェックでstrict
引数の確認を行っています。
それからiterables
の各オブジェクトのイテレーターを取得します。
そしてresult holder
と呼ばれる結果を格納するタプルを作成しています。
zipobject
というオブジェクトを作成し、これらのイテレーターやresult holder
をオブジェクトに格納しています。
cpython/Python/bltinmodule.c at main · python/cpython · GitHub
zipオブジェクトをリストに変換する場合
zip
オブジェクトをリストに変換する場合はlist
クラスに渡します。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
it = zip(lis1, lis2)
print(list(it))
出力結果。
[(1, 'cat'), (2, 'dog'), (3, 'bird')]
zipオブジェクトをタプルに変換する場合
zip
オブジェクトをタプルに変換する場合はtuple
クラスに渡します。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
it = zip(lis1, lis2)
print(tuple(it))
出力結果。
((1, 'cat'), (2, 'dog'), (3, 'bird'))
zipオブジェクトを辞書に変換する場合
zip
オブジェクトを辞書に変換する場合はdict
クラスに渡します。
names = ['Mike', 'Tama', 32, 23]
ages = [3, 7, 1, 4]
it = zip(names, ages)
name_and_ages = dict(it)
print(name_and_ages)
出力結果。
{'Mike': 3, 'Tama': 7, 32: 1, 23: 4}
nextで要素を取り出す場合
zip
オブジェクトをnext
に渡すとzip
された要素を順次取得できます。
内部のポインタが最後まで進んだ場合、StopIteration
が発生します。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
it = zip(lis1, lis2)
try:
print(next(it))
print(next(it))
print(next(it))
print(next(it)) # <- ここでStopIterationが発生
except StopIteration:
print('stop!')
出力結果。
(1, 'cat')
(2, 'dog')
(3, 'bird')
stop!
CPythonのnextの実装
CPytohn3.13におけるnext()
の実装はcpython/Python/bltinmodule.c
のzip_next()
です。
この関数ではzip
オブジェクトのresult
メンバ変数に処理の結果を格納しています。
このresult
はタプル型のオブジェクトです。
result
の参照カウントが1か、それ以外で分岐しています。
1の場合はzip
オブジェクトのresult
にそのまま要素を格納していきます。
それ以外の場合はresult
の代わりにタプルを新しく作りそれに要素を格納して返しています。
要素の格納処理が終わったらcheck
ラベルにgoto
(ジャンプ)して、エラーチェックしています。
つまり前半で格納処理、後半でエラーチェック、という感じです。
cpython/Python/bltinmodule.c at main · python/cpython · GitHub
複数のイテラブルなオブジェクトをzipする
冒頭のコードですが、zip
で複数のリストをまとめられます。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
for a, b in zip(lis1, lis2):
print(a, b)
実行結果。
1 cat
2 dog
3 bird
また、異なるタイプのイテラブルなオブジェクトもまとめることができます。
lis = [1, 2, 3]
tpl = ['cat', 'dog', 'bird']
dic = {'a': 1, 'b': 2, 'c': 3}
for a, b, c in zip(lis, tpl, dic):
print(a, b, c)
実行結果。
1 cat a
2 dog b
3 bird c
イテラブルなオブジェクトの長さが違う場合
長さが違うイテラブルなオブジェクトをzip
した場合、zip
は長さが短いほうに合わせます。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog']
for a, b in zip(lis1, lis2):
print(a, b)
1 cat
2 dog
itertools.zip_longestで足りない分の要素を埋める
itertools
モジュールのzip_longest
を使うと、イテラブルなオブジェクト群の長さの長いほうに合わせて要素が生成されます。
デフォルトでは足りない要素はNone
になります。
from itertools import zip_longest
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog'] # <- こっちが短い
for a, b in zip_longest(lis1, lis2):
print(a, b)
実行結果。
1 cat
2 dog
3 None
fillvalue
引数に、足りない要素を埋めるときの値を指定することができます。
from itertools import zip_longest
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog']
for a, b in zip_longest(lis1, lis2, fillvalue='unknown'):
print(a, b)
実行結果。
1 cat
2 dog
3 unknown
zipしたイテラブルなオブジェクトをもとに戻す
zip
オブジェクトに*
を使うとzip
したイテラブルなオブジェクトを気持ち元に戻すことができます。
lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']
a, b = zip(*zip(lis1, lis2))
print(a)
print(b)
出力結果。
(1, 2, 3)
('cat', 'dog', 'bird')
リストがタプルになっているので正確には「元に戻ってる」とは言えませんが、支障はないでしょう。
zipの評価タイミング
zip
は事前にイテラブルなオブジェクトをすべてまとめることはせず、必要になった時点で要素をまとめます。
たとえばzip
に巨大なrange
を渡したとしても、zip
は要素を事前作成しないので処理が重くなることはありません。
↓の場合、要素がまとめられるのはnext
が呼び出された時点です。
# 巨大なrangeを渡してもすぐ戻り値にアクセスできる
it = zip(range(100000000), range(100000000))
print(next(it))
print(next(it))
print(next(it))
実行結果。
(0, 0)
(1, 1)
(2, 2)
もちろん巨大なrange
を使ったzip
オブジェクトをlist
やtuple
などに渡せば一括変換が発生するので処理は重くなります。
処理を軽くしたい場合はnext
を使うなどして工夫しましょう。
zipの使用例
zip
の使用例です。
文字列のリストと整数のリストから辞書を作る
名前の入ったリストと年齢の入ったリストから、名前をキーにした辞書を生成します。
names = ['Mike', 'Tama', 'Pochi']
ages = [3, 7, 1]
it = zip(names, ages)
d = dict(it)
for k, v in d.items():
print(k, v)
出力結果。
Mike 3
Tama 7
Pochi 1
問題
Q1: zip
の戻り値はなにか答えよ
- リスト
- 辞書
- zipオブジェクト
Q2: zip
の評価タイミングとして正しいものを答えよ
- zipは事前生成せずに必要になったら要素を生成する
- zipはzipが呼ばれた時点ですべての要素を生成する
- zipはnextに渡されると事前生成しておいた要素を返す
Q3: zip
に異なる長さのイテラブルなオブジェクトを渡したときの挙動を答えよ
- 長いほうに合わせる
- 短いほうに合わせる
- エラーが発生する
正解
Q1: 3
Q2: 1
Q3: 2