のどあめ

ググってもなかなか出てこなかった情報を垂れ流すブログ。

MessagePackでgolang-python間でデータ受け渡しする

データをバイナリファイルとして保存すると読み込みが早くて良いですが、 OSや言語が変わると読み込むことができません。

研究室時代には、この問題に対処するためにJSONとバイナリを保存しておいて、 バイナリファイルの読み込みに失敗したらJSONを読み込むという意味不明な実装をしていました。

しかし、最近同僚にMessagePackというものがあることを教えてもらいました。

公式サイトからの引用

MessagePack is an efficient binary serialization format.
It lets you exchange data among multiple languages like JSON. But it's faster and smaller.

つまり、これを使えば万事解決するわけですね。 (同僚いわく、MessagePackを知らないエンジニアはモグリだとか・・・はい。)

今回は、試しにMessagePack(とredis)を使ってgoとpython間でデータのやりとりに挑戦します。

なお、あとで紹介するコードはこちらのgithubにも公開しています。 github.com

準備

データの書き出し先としてredisを使うので、Redisのインストールして起動しておきます。

$ redis-server

また、pythonとgoでMessagePackを扱うパッケージをインストールします。

$ pip install msgpack-python
$ go get gopkg.in/vmihailenco/msgpack.v2

さらに、goでRedisを扱うパッケージを入れます。

$ go get gopkg.in/redis.v3

redigoというパッケージもありますが、 こちらのほうが使いやすいです(個人的に)。名前に惑わされてはいけません。

実装

  • python or goの一方でRedisにList, Mapを書き込む
  • 他方でRedisからList, Mapを受け取る

というプログラムを実装します。

python → go

put.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import msgpack
import redis


if __name__ == "__main__":
    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    # packing
    packed_ls_int = msgpack.packb(range(10))
    packed_ls_float = msgpack.packb([-1.0, 2.02, 5.04])
    packed_dic = msgpack.packb({u'pykey1': u'pyval1', u'pykey2': u'pyval2'})
    # put
    r.set("py_ls_int", packed_ls_int)
    r.set("py_ls_float", packed_ls_float)
    r.set("py_map", packed_dic)

get.go

package main

import (
    "fmt"
    "gopkg.in/redis.v3"
    "gopkg.in/vmihailenco/msgpack.v2"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    redis_client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
    var unpacked_map map[string]string
    var unpacked_ls_int []int
    var unpacked_ls_float []float64
    {
        msg, msgpack_err := redis_client.Get("py_map").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_map)
    }
    {
        msg, msgpack_err := redis_client.Get("py_ls_int").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_ls_int)
    }
    {
        msg, msgpack_err := redis_client.Get("py_ls_float").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_ls_float)
    }
    fmt.Println(unpacked_map)
    fmt.Println(unpacked_ls_int)
    fmt.Println(unpacked_ls_float)
}

実行結果

$ python put.py
$ go run get.go
map[pykey1:pyval1 pykey2:pyval2]
[0 1 2 3 4 5 6 7 8 9]
[-1 2.02 5.04]

go -> python

put.go

package main

import (
    "gopkg.in/redis.v3"
    "gopkg.in/vmihailenco/msgpack.v2"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    redis_client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
    dic := map[string]string{"gokey1": "goval1", "gokey2": "goval2"}
    ls_int := []int{1, 3, 5, 8}
    ls_float := []float64{2.3, 6.4, 5.5}
    {
        msg, msgpack_err := msgpack.Marshal(dic)
        check(msgpack_err)
        check(redis_client.Set("go_map", msg, 0).Err())
    }
    {
        msg, msgpack_err := msgpack.Marshal(ls_int)
        check(msgpack_err)
        check(redis_client.Set("go_ls_int", msg, 0).Err())
    }
    {
        msg, msgpack_err := msgpack.Marshal(ls_float)
        check(msgpack_err)
        check(redis_client.Set("go_ls_float", msg, 0).Err())
    }
}

get.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import msgpack
import redis


if __name__ == "__main__":
    r = redis.StrictRedis(host='localhost', port=6379, db=0)

    packed = r.get("go_map")
    dic = msgpack.unpackb(packed, encoding='utf-8')
    packed = r.get("go_ls_int")
    ls_int = msgpack.unpackb(packed, encoding='utf-8')
    packed = r.get("go_ls_float")
    ls_float = msgpack.unpackb(packed, encoding='utf-8')

    print dic
    print ls_int
    print ls_float

実行結果

$ go run put.go
$ python get.py
{u'gokey2': u'goval2', u'gokey1': u'goval1'}
[1, 3, 5, 8]
[2.3, 6.4, 5.5]

まとめ

複雑な構造体などはパーサを定義する必要があるみたいですが、 リストやマップ程度であればこのように簡単に受け渡しできます。

試してはいませんが、他言語間でもいい感じに使えるのではないでしょうか。

くれぐれも俺俺フォーマットなんて作らず、MessagePackを使っていきましょう!