カテゴリー

Python基礎

【python3&zip関数】python3系にてzipは一回使ったら消える

2021年5月22日

こんな人にオススメ

python3系でzip関数を定義、使用した後に再度使用したら中身が空なんだが、なぜ?

ということで、今回はzip関数の中身が空になる問題について解説する。これは実際に執筆者本人が体験したことだ。具体的には以下のコードを作成した際に、pythonのバージョンで挙動が異なる。

a = [1, 2, 3]
b = (10, 2, 30)
z = zip(a, b)

for i in range(3):
    print('loop{}'.format(i + 1))
    for j in z:
        print(j)

このコードではalistを、btupleを定義している。そしてa, bzzipとしてまとめた。このzのそれぞれの要素をjのループで取り出す、という操作をiのループで合計3回行なっている。

python2系での挙動は以下。ループごとに要素が取り出されている。

# loop1
# (1, 10)
# (2, 2)
# (3, 30)
# loop2
# (1, 10)
# (2, 2)
# (3, 30)
# loop3
# (1, 10)
# (2, 2)
# (3, 30)

一方でpython3系での挙動は以下。第2, 3ループで要素が取り出されなくなっている。

# loop1
# (1, 10)
# (2, 2)
# (3, 30)
# loop2
# loop3

要素が取り出されなくなっていることで、自身が行いたい操作ができなかったのでいろいろ調べて本記事でまとめる。

python環境は以下。今回はpython2系と3系を使用する。

  • Python 2.7.16
  • Python 3.9.4
  • numpy 1.20.3

運営者のメガネです。YouTubeTwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

zipの出力の確認

まずは現状確認として、2系と3系での変数の出力を行う。

2系ではlist、3系ではzip

内容出力は変数として作成したa, b, zとそのtype。コードは以下。

# coding: UTF-8
import platform

# pythonのバージョンを出力
version = platform.python_version()

a = [1, 2, 3]
b = (10, 2, 30)
z = zip(a, b)

# zipは2系ではlist、3系ではzip
result = [
    'version: {}'.format(version),
    'a: {}'.format(str(a)),
    'type(a): {}'.format(str(type(a))),
    'b: {}'.format(str(b)),
    'type(b): {}'.format(str(type(b))),
    'z: {}'.format(str(z)),
    'type(z): {}'.format(str(type(z))),
    'list(z): {}'.format(str(list(z))),
    'type(list(z)): {}'.format(str(type(list(z)))),
]

with open('zip_behavior{}.txt'.format(version[0]), mode='w') as f:
    # 改行は指定しないと入らない
    f.write("\\n".join(result))

printで出力しない理由は、バージョンによるprintの違いだ。

  • 2系:print文
  • 3系:print関数

具体的には以下の感じ。

  • 2系:print a
  • 3系:print(a)

try文でpythonのバージョンごとに出力を変えようとしたが、実行時のSyntaxErrorで引っ掛かるのでやむを得ずファイルへと出力した。さらによく使用するf-stringもpython3.6からの機能なので今回は.formatで文字列の結合を行なっている。

ファイルの中身は以下。まずはpython2系。

version: 2.7.16
a: [1, 2, 3]
type(a): <type 'list'>
b: (10, 2, 30)
type(b): <type 'tuple'>
z: [(1, 10), (2, 2), (3, 30)]
type(z): <type 'list'>
list(z): [(1, 10), (2, 2), (3, 30)]
type(list(z)): <type 'list'>

続いて3系。

version: 3.9.4
a: [1, 2, 3]
type(a): <class 'list'>
b: (10, 2, 30)
type(b): <class 'tuple'>
z: <zip object at 0x1013fda40>
type(z): <class 'zip'>
list(z): [(1, 10), (2, 2), (3, 30)]
type(list(z)): <class 'list'>

abtypetypeclassかの表記の違いがあるが今回はここはスルー。注目いただきたいのは中央付近のzの出力。

  • 2系:list
  • 3系:zip

として出力されている。そして、3系ではlistに変換すると中身を確認することが出来るようだ。

python3のzipはイテレータ

色々と調べてみるとどうやら3系でのzipは「イテレータ」と呼ばれるものらしい。参考にしたサイトについては最後に記載する。

イテレータとは

そもそもイテレータ(iterator)とはなんなのかということだが、ざっくりまとめると以下のようだ。

  • イテラブルなオブジェクトの中の1つ
  • 要素を取り出す度にどこまで取り出したのかを記憶(保持)する
  • ただし、取り出した要素は取り出すごとに消滅する

なるほど、取り出すごとに要素が削除されるのか。だから、2回目以降のループでは中身が消えたのか。

しかし、イテラブルとは。ということで次でイテレータとイテラブルについて解説する。

イテレータとイテラブル

イテレータと似た言葉で「イテラブル」というものがある。イテラブルはざっくりまとめると以下のようだ。

  • 要素に繰り返しアクセスして取得できるもの(オブジェクト)
  • すなわち繰り返して使用可能なもの(オブジェクト)
  • for i in ...の「...」におけるもの
  • list, tuple, rangeなどがイテラブル
  • ただし、要素がどこまで取り出したのかは保持しない

ふむふむ。繰り返して使用できるものがイテラブルということか。その中でも、取り出した順番を保持しつつ、取り出すごとに要素を消すのがイテレータ。

分類分けとしては以下の感じ。他にも該当するものがあるが、代表的なものを記載。

  • not イテレータ(イテラブル、取り出し場所の保持ナシ、使用しても要素は消えない)
    • 文字列
    • list
    • tuple
    • range
    • dictdict.keys(), dict.values(), dict.items()
    • set
  • イテレータ(イテラブル、取り出し場所の保持アリ、使用したら要素は消える)
    • iter(list)とか、 not イテレータをiterしたもの
    • ジェネレータ

「ジェネレータ」。新しい単語が出てきたので次章で説明する。また、iterは次節で例を示す。

iternext

前節でiterなるものが出てきたが、これは「イテラブル」を「イテレータ」にするための関数。そして、イテレータの特徴として以下のものがあった。

  1. 要素をどこまで取り出したのかを保持
  2. 取り出し次第その要素を削除

iter関数を使用した時に実際にイテレータとなるかどうかを確かめるに以下のコードを作成した。実際にイテレータになっていることがわかる。

また、要素の保持と削除を確かめるために以下のコードを作成した。ここでnextなる関数を使用する。これはイテレータの要素を順番に出力することが出来るというもの。例えば以下のような例。

a, bの要素数は3なので1, 2, 3回目のnextは正常に動く。途中に他の出力が入っても大丈夫。

# z: <zip object at 0x101513880>
# 1回目のnext: (1, 10)
# 2回目のnext: (2, 2)
# 途中に文字列を挟む
# 3回目のnext: (3, 30)

しかし、4回目のnextをするとStopIterationというエラーが発生する。これは、次に取り出す要素が無くなったから。

#     print('4回目のnext: {}'.format(next(z)))
# StopIteration

そして、取り出し次第その要素を削除という点に関しては以下のコードで確認することができる。listにして中身を確認すると、実際に中身が消えていることが確認できる。

なお、zを再定義するという点については後ほど述べる。

この例だと2回nextをしているので前2つの要素が削除され、list(z)が最後の330だけになっている。

イテレータとイテラブルまとめ

執筆者的なイテレータとイテラブルのまとめ。

  • イテラブルの中にイテレータという分類がある
  • イテラブル自体の意味は繰り返し使用可能なもの(オブジェクト)
  • その中でも以下の特徴を持つのがイテレータ
    1. 要素をどこまで取り出したのかを保持
    2. 取り出し次第その要素を削除

イテレータとジェネレータ

さて、前章で「ジェネレータ」という言葉が出てきた。イテレータにジェネレータ。似たような言葉だが、意味合いとしては並列ではないようだ。

あくまでも「イテレータを作成するための関数」のようだ。

ジェネレータとは

上でも書いたように、ジェネレータとは「イテレータを作成するための関数」。その他の説明としては、「要素を取り出したいときにその都度取り出せる関数」という感じ。

ジェネレータとイテレータの違い

イテレータがlistとかの既に形の決まっているものであるなら、ジェネレータはdef ...():のように決まった形のないもの、というイメージだろうか。どちらも配列として機能させることはできるが、より自由度が高いのがジェネレータ?か?なのか?

ただ、明確に異なるのが以下。

  • return: returnを使って一括で要素を取り出す
  • generator: yieldで要素ごとに取り出す

yieldは次節で例を示すが、nextと同じような役割。

ジェネレータのメリット

ジェネレータのメリットは要素ごとに取り出すことが出来るという点。ということは、大量のデータを順番に出力する際に

  • return: 一旦、全てのデータを作成し、必要な部分を取り出す
  • generator: 必要な時に必要な分だけ取り出す

という違いが効いて、実行時間に大きく差が出る。例えば以下のコード。100×100のデータを500,000回作成し、必要なインデックスだけ出力するというもの。

import numpy as np
import time

まずはreturnを使用した場合。

実行時間は7秒程度。一方でgeneratorを使用すると、実行時間は0.001秒程度と5,000倍以上も早いという結果となった。

ただ、[next(gen) for i in range(100 - 2)]とあるように、好きなインデックスを直接持ってくることはできず、そのインデックスまでnextを繰り返さないといけない。

しかし、かなり重い処理をしたい、かつ、順番にデータを使用する、という場面であればgeneratorを使用するとかなり早く処理を行うことが出来ると思われる。

zipを複数回使用するためには

初めの問題へと戻り、zipの中身が消える問題は以下が原因であった。

  1. python3からはzipはイテレータ扱い
  2. イテレータは一回使用するとその要素は削除される

では、zipを複数回使用したい場合はどうすれば良いのか。

毎回定義する

まずはシンプルに毎回定義する。zが消えるのであれば、次に使うまでに定義すればよい。

a = [1, 2, 3]
b = (10, 2, 30)

for i in range(3):
    z = zip(a, b)
    print('loop{}'.format(i + 1))
    for j in z:
        print(j)
# loop1
# (1, 10)
# (2, 2)
# (3, 30)
# loop2
# (1, 10)
# (2, 2)
# (3, 30)
# loop3
# (1, 10)
# (2, 2)
# (3, 30)

イテラブルに変換

次はもはやイテレータを使わない方法。イテレータを使用しなければ今回の問題は起きない。

z = list(zip(a, b))
print(z)
for i in range(3):
    print('loop{}'.format(i + 1))
    for j in z:
        print(j)
# [(1, 10), (2, 2), (3, 30)]
# loop1
# (1, 10)
# (2, 2)
# (3, 30)
# loop2
# (1, 10)
# (2, 2)
# (3, 30)
# loop3
# (1, 10)
# (2, 2)
# (3, 30)

zipまとめ

本記事をまとめると。

  1. python3のzipはイテレータ
  2. イテレータは要素の取り出し場所を保持
  3. イテレータは要素を取り出した後はその要素を削除
  4. ジェネレータはイテレータを作るための関数
  5. iter()でイテレータを作成
  6. next()でイテレータの次の要素を取り出し
  7. yield()でジェネレータの次の要素を取り出し

日々の気づきを自分の中にzipする

今回は執筆者自身が実際につまづいた、疑問に思った事案について調べて紹介した。厳密には違う部分があるかもしれないが、今回の内容を皮切りにさらに知識を蓄積したい。

このような日々の気づきの積み重ねが自分を作っていると思う。

参考にさせていただいたサイト

関連記事

テキストを置き換える
【Mac&スクショ名】Macのスクショのファイル名を自動変更

こんな人にオススメ Macでスク ...

続きを見る

【PEP8&flake8】pythonにおけるPEP8とflake8

こんな人にオススメ pythonにつ{ ...

続きを見る

スイッチボット

2022/11/28

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/11/28

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

完全ワイヤレスイヤホン(TWS)

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

完全ワイヤレスイヤホン(TWS)

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-Python基礎
-,