PythonからGoにTranspileできるツールがOSSになったそうなので早速触ってみた。 詳しくはGithubのリポジトリを参照。ドキュメントとかはまだちゃんとしたのは無いみたい。
「ぐらむぱい」って呼べばいいのかな。
インストール
cloneしてきてセットアップ。READMEに書かれてるMethod2(.pyファイルをトランスパイル)をやる。
``` $ git clone https://github.com/google/grumpy.git $ cd grumpy $ make $ export GOPATH=$PWD/build $ export PYTHONPATH=$PWD/build/lib/python2.7/site-packages ```Hello, world
適当にコード書いてみて変換する。なお、以下は全てチェックアウトしたプロジェクトフォルダの中で行う(自分のプロジェクト内でも使ってみたのは後述)
``` hello.py --------print ‘Hello, world’
<p>コードを見るとわかるようにgrumpyはpython2系の変換で3系は駄目みたい。あんまりちゃんとpyhton書かないのでわからないけどそういう用途らしい。</p>
$ ./tools/grumpc hello.py > hello.go
<p>変換されるコードはこんな感じ:</p>
hello.go
package main
import (
πg “grumpy”
πos “os”
)
func initModule(πF πg.Frame, _ []πg.Object) (πg.Object, *πg.BaseException) {
var πTemp001 []πg.Object
_ = πTemp001
var πE πg.BaseException; _ = πE
for ; πF.State() >= 0; πF.PopCheckpoint() {
switch πF.State() {
case 0:
default: panic(“unexpected function state”)
}
// line 1: print ‘Hello, world’
πF.SetLineno(1)
πTemp001 = make([]πg.Object, 1)
πTemp001[0] = πg.NewStr(“Hello,\x20world”).ToObject()
if πE = πg.Print(πF, πTemp001, true); πE != nil {
continue
}
return nil, nil
}
return nil, πE
}
var Code *πg.Code
func main() {
Code = πg.NewCode(”
接頭辞にπがついてるけど、pyでπなのかな。他にもよくわからない文字が使われているけど、ちゃんとGoでコンパイルもできるし実行もできる。
``` $ go build -o hello hello.go $ ./hello >>> Hello, world ```モジュール依存関係
通常一枚のファイルだけであることはあんまりないと思う(ちょっとしたバッチプログラムならやるだろうけど)し、普通は自作のモジュール使ったりして機能を分けたりすると思う。 モジュールの依存関係を保ったままやるのはちょっとしんどい。
``` hello.py --------import hello_mod
print hello_mod.say()
hello_mod.py
def say(): return ‘Hello, world’
<p>grumpcは一枚のpyファイルのみ変換するので、それぞれを変換しないといけない。まずはhello.pyを変換すると、</p>
hello.go
package main
import (
πg “grumpy”
πgrumpyΓlibΓhellomod “grumpy/lib/hellomod”
πos “os”
)
func initModule(πF πg.Frame, _ []πg.Object) (πg.Object, *πg.BaseException) {
ßhellomod := πg.InternStr(“hellomod”)
ßsay := πg.InternStr(“say”)
var πTemp001 *πg.Object
_ = πTemp001
var πTemp002 []πg.Object
_ = πTemp002
var πTemp003 πg.Object
_ = πTemp003
var πE *πg.BaseException; _ = πE
for ; πF.State() >= 0; πF.PopCheckpoint() {
switch πF.State() {
case 0:
default: panic(“unexpected function state”)
}
// line 1: import hellomod
πF.SetLineno(1)
if πTemp002, πE = πg.ImportModule(πF, “hellomod”, []πg.Code{πgrumpyΓlibΓhellomod.Code}); πE != nil {
continue
}
πTemp001 = πTemp002[0]
if πE = πF.Globals().SetItem(πF, ßhellomod.ToObject(), πTemp001); πE != nil {
continue
}
// line 3: print hellomod.say()
πF.SetLineno(3)
πTemp002 = make([]*πg.Object, 1)
if πTemp001, πE = πg.ResolveGlobal(πF, ßhellomod); πE != nil {
continue
}
if πTemp003, πE = πg.GetAttr(πF, πTemp001, ßsay, nil); πE != nil {
continue
}
if πTemp001, πE = πTemp003.Call(πF, nil, nil); πE != nil {
continue
}
πTemp002[0] = πTemp001
if πE = πg.Print(πF, πTemp002, true); πE != nil {
continue
}
return nil, nil
}
return nil, πE
}
var Code *πg.Code
func main() {
Code = πg.NewCode(”
importの部分にhello_modが追加されている。このimportするパスが重要で、このパスに従っ変換結果を設置する必要がある。 またhello_mod.pyはモジュールなので-modnameオプションを指定して変換する。
``` $ tools/grumpc -modname hello_mod hello_mod.py > build/src/grumpy/lib/hello_mod/hello_mod.go ```build/src/以下はGOPATHの管理下なので、ここにディレクトリを作成して配置すると上手くmainからimportができる。変換結果は以下の通り。
``` build/src/grumpy/lib/hello_mod/hello_mod.go ------------------------------------------package hellomod
import (
πg “grumpy”
)
func initModule(πF πg.Frame, _ []πg.Object) (*πg.Object, *πg.BaseException) {
ßsay := πg.InternStr(“say”)
var πTemp001 *πg.Object
_ = πTemp001
var πTemp002 []πg.FunctionArg
_ = πTemp002
var πE *πg.BaseException; _ = πE
for ; πF.State() >= 0; πF.PopCheckpoint() {
switch πF.State() {
case 0:
default: panic(“unexpected function state”)
}
// line 1: def say():
πF.SetLineno(1)
πTemp002 = make([]πg.FunctionArg, 0)
πTemp001 = πg.NewFunction(πg.NewCode(“say”, “hellomod.py”, πTemp002, 0, func(πF πg.Frame, πArgs []πg.Object) (*πg.Object, *πg.BaseException) {
var πE *πg.BaseException; _ = πE
for ; πF.State() >= 0; πF.PopCheckpoint() {
switch πF.State() {
case 0:
default: panic(“unexpected function state”)
}
// line 2: return ‘Hello, world’
πF.SetLineno(2)
return πg.NewStr(“Hello,\x20world”).ToObject(), nil
return nil, nil
}
return nil, πE
}), πF.Globals()).ToObject()
if πE = πF.Globals().SetItem(πF, ßsay.ToObject(), πTemp001); πE != nil {
continue
}
return nil, nil
}
return nil, πE
}
var Code *πg.Code
func init() {
Code = πg.NewCode(”
init()の部分にExitするコードがなくなり、Codeのexportのみになっている。これで実行するとちゃんとimportされてhello_mod.say()が実行される。
``` $ go build -o hello hello.go $ ./hello >>> Hello, world ```モジュール側のコードを見て気づいたけど、Codeって変数がexportされるから、複数ファイルから構成されるモジュールはどうなるんだろ?多分出来なさそうな気がする。。
自分の作業フォルダでやる
毎回チェックアウトしたフォルダでやるのはつらいので、プロジェクト毎に変換できるようにしてみる。
``` # grumpcコマンドパスを通す $ export PATH=$PATH:/path/to/grumpy/toolscd path/to/project
GOPATHを追加する(あまりよくないかも)
GOPATH:$PWD
別途importを解決できるようにここにも作る
$ mkdir -p src/grumpy/lib/hello_mod
変換する
grumpc -modname hellomod hellomod.py > src/grumpy/lib/hellomod/hellomod.go
コンパイルして実行
./hello
Hello, world
<p>GOPATHを複数作るのは微妙かもしれないけど、こうすることでgitからチェックアウトしたフォルダで作業しなくても良くなった。</p>
<p>ちなみに、pipとかでインストールした依存関係は現状解決できないっぽい。<a href="https://github.com/google/grumpy/issues/5">issue</a> が上がってるからそのうち解決されるかも。 試しにrequestsモジュールを変換しようと試みたけどエラーになったりでまだ無理っぽい感じだった。。 これを書いてる間にもissue/PRが増えてるみたいなので活発みたい。</p>
<h2 id="変換したモジュールをgoから使う">変換したモジュールをGoから使う</h2>
<p>モジュールとして変換したものはちょっとゴニョゴニョしてGoからコールすることができるらしい。hello_modモジュールを例にあげると以下のような感じ。</p>
main.go
package main import ( “hellomod” “grumpy” ) func main() { f := grumpy.NewRootFrame() mods, _ := grumpy.ImportModule(f, “hellomod”, []*grumpy.Code{hello_mod.Code}) foo, _ := grumpy.GetAttr(f, mods[0], grumpy.NewStr(“say”), nil) foo.Call(f, nil, nil) }
<p>grumpyパッケージは配置されているので、そのメソッド経由でコールすることができる。</p>
<h2 id="まとめ">まとめ</h2>
<p>実際に使うことは無いかもだけど、pythonで書かれたモジュールをGoに変換して使うことができるのは便利かも。 もしdependencyなパッケージを含めて変換できたら、Goからsupervisorを使えたりするのは夢があると思う。覚えておいて損はないかも。 python3で書かれたやつも変換できるといいなぁ。</p>
<p>cgoでC/C++のコードを同居させられるし、色んな言語を混ぜ込んでGoが実行できるのは面白いかも。</p>
<p>ここ最近になってようやくこういうちょっと試すような時間が取れるようになったので、 コツコツやっていこうと思う。</p>
<p>現場からは以上です。</p>