こしあん
2019-09-24

Pythonでzipを使ってJSONライクなdictをいい感じにforループで回す

Pocket
LINEで送る


Pythonでzip関数を使ってJSONライクな辞書(dict)を、いい感じにforループで要素ごとに抽出する例を解説します。ほぼワンライナーでできるので簡単です。

想定

こんなJSONライクな辞書があったとします。JSONからパースした場合でもいいですね。

data = {
    "in_channels": [1024, 1024, 512, 256, 128],
    "out_channels": [1024, 512, 256, 128, 64],
    "upsample": [True, True, True, True, True],
    "resolution": [8, 16, 32, 64, 128],
    "attention": {
        8: False, 16:False, 32:False, 64:True, 128:False
    }
}

これをforループで回して、各配列の中身を取得したいのです。例えば、こんな出力にしたいのです。

(1024, 1024, True, 8, False)
(1024, 512, True, 16, False)
(512, 256, True, 32, False)
(256, 128, True, 64, True)
(128, 64, True, 128, False)

実はこれはほぼワンライナーでできます。それを見ていきましょう。

zip関数の基本

まずはzip関数の基本から。zip関数は

for a, b in zip([1, 2, 3], [4, 5, 6]):
    print(a, b)

とすると

1 4
2 5
3 6

のように出力するのがzip関数です。要はzip関数の中に複数の一次元配列を放り込めればいいのです。

ただしdictの配列数(要素数)は可変なので、真面目にzip(a, b, …)のように書くのはアホらしいです。どうすればいいでしょう?

アンパックのアスタリスク「*」

答えは、シーケンスのアンパック・アスタリスク(*)を使いましょう。可変長引数でも使われるやつですね。zipと組み合わせると先程の例は次のようにも書けます。

data = [[1, 2, 3], [4, 5, 6]]
for a, b in zip(*data):
    print(a, b)

これは先程と同じ出力になります。

1 4
2 5
3 6

dictの値だけ配列の配列に格納するには、dict.values()を使えばいいのでこれでできそうな気がします。

ネストされた辞書のケア

辞書の中身がすべて配列だったらOKですが、values()→アンパック→zipだとネストされた辞書が要素にあったときに困ります。例えば、

def main():
    data = {
        "in_channels": [1024, 1024, 512, 256, 128],
        "out_channels": [1024, 512, 256, 128, 64],
        "upsample": [True, True, True, True, True],
        "resolution": [8, 16, 32, 64, 128],
        "attention": {
            8: False, 16:False, 32:False, 64:True, 128:False            
        }
    }
    for seq in zip(*data.values()):
        print(seq)

このようにすると、

(1024, 1024, True, 8, 8)
(1024, 512, True, 16, 16)
(512, 256, True, 32, 32)
(256, 128, True, 64, 64)
(128, 64, True, 128, 128)

ネストされた辞書(attention)のキーだけ抽出されてしまいました。ネストされた辞書の値がほしいのでちょっとこれは困りました。

そこで以下のようにループ側で対策します。

# ネストされた辞書対策
def main():
    data = {
        "in_channels": [1024, 1024, 512, 256, 128],
        "out_channels": [1024, 512, 256, 128, 64],
        "upsample": [True, True, True, True, True],
        "resolution": [8, 16, 32, 64, 128],
        "attention": {
            8: False, 16:False, 32:False, 64:True, 128:False            
        }
    }
    for seq in zip(*(v.values() if type(v) is dict else v for v in data.values())):
        print(seq)

単純にvalues()で出てきた値がdictかどうかを調べているだけです。もしvがネストされた辞書だったら、さらにそれのvalues()を読みに行くことで、値側を取りに行くようにしています。

結果は以下の通りです。

(1024, 1024, True, 8, False)
(1024, 512, True, 16, False)
(512, 256, True, 32, False)
(256, 128, True, 64, True)
(128, 64, True, 128, False)

これで目的の結果になりました。

注意点

この例はPython3.7以上で行っています。Python3.7以上ではdictの順番が保証されるのが明確に言語仕様として追加されたのでOrderedDictを使わなくてもキーの順番保証ができるようになっています。Pythonのバージョンが古い場合は、OrderedDictを使って順番のケアをしてある必要があるかもしれません(あるいはライブラリによってはOrderedDictでパースしているケースがあるので、そのタイプのケアなど)。

Related Posts

PyTorchでガウシアンピラミッド+ラプラシアンピラミッド(Gaussian/Laplacian ... Progressive-GANの論文で、SWD(Sliced Wasserstein Distance)が評価指標として出てきたので、その途中で必要になったガウシアンピラミッド、ラプラシアンピラミッドをPyTorchで実装してみました。これらのピラミッドはGAN関係なく、画像処理一般で使えるものです...
Numpyの配列に対して「最も多く存在する値」を求める方法... アンサンブル学習などで、Numpyの配列のある軸に対して「最も多く存在する値」を求めたい、つまり「多数決」をしたいことがあります。その方法を見ていきます。 最も大きい値がmax, 最も大きい値が存在するインデックスがargmax, では「最も多く存在する値」は? 配列のある軸に対して、「最も大...
Kerasで重みを共有しつつ、必要に応じて入力の位置を変える方法... Kerasで訓練させて、途中から新しく入力を作ってそこからの出力までの値を取りたいということがたまにあります。例えば、Variational Auto Encoderのサンプリングなんかそうです。このあまり書かれていないのでざっとですが整理しておきます。 こういうことをやりたい 言葉で書いても...
Pillowでグレースケール化するときに3チャンネルで出力するテクニック... カラー画像をグレースケール化すると1チャンネルの出力になりますが、カラー画像と同時に扱うと整合性からグレースケールでも3チャンネルで欲しいことがあります。Numpyのブロードキャストを使わずに簡単に3チャンネルで出力する方法を書きます。 グレースケール化してカラー化すればOK これでOK i...
TensorFlow2.0のGradientTapeを複数使う場合のサンプル... TF2.0系で少し複雑なモデルを訓練するときに、GradientTapeを複数使うことがあります。例として、微分を取りたい場所が2箇所あるケースや、2階微分を取りたいケースを挙げます。場合によって微妙に書き方が違うので注意が必要です。 微分を取りたい場所が2箇所あるケース 簡単な例にします。次...
Pocket
Delicious にシェア

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です