Pythonのzipの使い方: 複数のイテラブルからイテレーターを作る

39, 2020-09-07

目次

Pythonのzip関数

Pythonには組み込み関数のzipがあります。

この関数は複数のリストなどのイテラブルなオブジェクトから、1つのzipオブジェクトを作るときに使われます。
たとえば複数のリストを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オブジェクトを作成します。

lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']

it = zip(lis1, lis2)

print(it)
print(type(it))
<zip object at 0x00000238443B5C88>
<class 'zip'>

zipの戻り値であるzip objectはイテラブルなオブジェクトです。

Help on zip object:

class zip(object)
 |  zip(*iterables) --> zip object
 |
 |  Return a zip object whose .__next__() method returns a tuple where
 |  the i-th element comes from the i-th iterable argument.  The .__next__()
 |  method continues until the shortest iterable in the argument sequence
 |  is exhausted and then it raises StopIteration.
 |
 |  Methods defined here:
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  __next__(self, /)
 |      Implement next(self).
 |
 |  __reduce__(...)
 |      Return state information for pickling.
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

このzipオブジェクトはリストやタプルに変換可能なほか、nextにも渡せるしfor文で回すこともできます。

リストに変換する場合

zipオブジェクトをリストに変換する場合はlistクラスに渡します。

lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']

it = zip(lis1, lis2)

print(list(it))

出力結果。

[(1, 'cat'), (2, 'dog'), (3, 'bird')]

タプルに変換する場合

zipオブジェクトをタプルに変換する場合はtupleクラスに渡します。

lis1 = [1, 2, 3]
lis2 = ['cat', 'dog', 'bird']

it = zip(lis1, lis2)

print(tuple(it))

出力結果。

((1, 'cat'), (2, 'dog'), (3, 'bird'))

辞書に変換する場合

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!

複数のイテラブルなオブジェクトを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オブジェクトをlisttupleなどに渡せば一括変換が発生するので処理は重くなります。
処理を軽くしたい場合は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の戻り値はなにか答えよ

  1. リスト

  2. 辞書

  3. zipオブジェクト

Q2: zipの評価タイミングとして正しいものを答えよ

  1. zipは事前生成せずに必要になったら要素を生成する

  2. zipはzipが呼ばれた時点ですべての要素を生成する

  3. zipはnextに渡されると事前生成しておいた要素を返す

Q3: zipに異なる長さのイテラブルなオブジェクトを渡したときの挙動を答えよ

  1. 長いほうに合わせる

  2. 短いほうに合わせる

  3. エラーが発生する


正解

Q1: 3
Q2: 1
Q3: 2