やったことだけ書く備忘録

grumpyを試してみた

PythonからGoにTranspileできるツールがOSSになったそうなので早速触ってみた。 詳しくはGithubのリポジトリを参照。ドキュメントとかはまだちゃんとしたのは無いみたい。


google/grumpy


「ぐらむぱい」って呼べばいいのかな。


インストール


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'
 

コードを見るとわかるようにgrumpyはpython2系の変換で3系は駄目みたい。あんまりちゃんとpyhton書かないのでわからないけどそういう用途らしい。



$ ./tools/grumpc hello.py > hello.go

変換されるコードはこんな感じ:



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.Object1)
        
πTemp001[0] = πg.NewStr("Hello,\x20world").ToObject()
        if 
πE πg.Print(πFπTemp001true); πE != nil {
            continue
        }
        return 
nilnil
    
}
    return 
nilπE
}
var 
Code *πg.Code
func main
() {
    
Code πg.NewCode("<module>""hello.py"nil0initModule)
    
π_os.Exit(πg.RunMain(Code))
}
 


接頭辞にπがついてるけど、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'
 


grumpcは一枚のpyファイルのみ変換するので、それぞれを変換しないといけない。まずはhello.pyを変換すると、



hello
.go
-------

package main
import 
(
    
πg "grumpy"
    
π_grumpyΓlibΓhello_mod "grumpy/lib/hello_mod"
    
π_os "os"
)
func initModule(πF *πg.Frame[]*πg.Object) (*πg.Object, *πg.BaseException) {
    
ßhello_mod := πg.InternStr("hello_mod")
    
ß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 hello_mod
        
πF.SetLineno(1)
        if 
πTemp002πE πg.ImportModule(πF"hello_mod", []*πg.Code{π_grumpyΓlibΓhello_mod.Code}); πE != nil {
            continue
        }
        
πTemp001 πTemp002[0]
        if 
πE πF.Globals().SetItem(πFßhello_mod.ToObject(), πTemp001); πE != nil {
            continue
        }
        
// line 3: print hello_mod.say()
        
πF.SetLineno(3)
        
πTemp002 make([]*πg.Object1)
        if 
πTemp001πE πg.ResolveGlobal(πFßhello_mod); πE != nil {
            continue
        }
        if 
πTemp003πE πg.GetAttr(πFπTemp001ßsaynil); πE != nil {
            continue
        }
        if 
πTemp001πE πTemp003.Call(πFnilnil); πE != nil {
            continue
        }
        
πTemp002[0] = πTemp001
        
if πE πg.Print(πFπTemp002true); πE != nil {
            continue
        }
        return 
nilnil
    
}
    return 
nilπE
}
var 
Code *πg.Code
func main
() {
    
Code πg.NewCode("<module>""hello.py"nil0initModule)
    
π_os.Exit(πg.RunMain(Code))
}
 


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 hello_mod
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.FunctionArg0)
        
πTemp001 πg.NewFunction(πg.NewCode("say""hello_mod.py"πTemp0020func(π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 nilnil
            
}
            return 
nilπE
        
}), πF.Globals()).ToObject()
        if 
πE πF.Globals().SetItem(πFßsay.ToObject(), πTemp001); πE != nil {
            continue
        }
        return 
nilnil
    
}
    return 
nilπE
}
var 
Code *πg.Code
func init
() {
    
Code πg.NewCode("<module>""hello_mod.py"nil0initModule)
}
 

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/tools

$ mkdir path/to/project
$ cd path/to/project

# GOPATHを追加する(あまりよくないかも)
$ export GOPATH=$GOPATH:$PWD

# 別途importを解決できるようにここにも作る
$ mkdir -p src/grumpy/lib/hello_mod

# 変換する
$ grumpc hello.py > hello.go
$ grumpc -modname hello_mod hello_mod.py > src/grumpy/lib/hello_mod/hello_mod.go

# コンパイルして実行
$ go build -o hello hello.go
$ ./hello
>>> Hello, world

GOPATHを複数作るのは微妙かもしれないけど、こうすることでgitからチェックアウトしたフォルダで作業しなくても良くなった。


ちなみに、pipとかでインストールした依存関係は現状解決できないっぽい。issue が上がってるからそのうち解決されるかも。 試しにrequestsモジュールを変換しようと試みたけどエラーになったりでまだ無理っぽい感じだった。。 これを書いてる間にもissue/PRが増えてるみたいなので活発みたい。


変換したモジュールをGoから使う


モジュールとして変換したものはちょっとゴニョゴニョしてGoからコールすることができるらしい。hello_modモジュールを例にあげると以下のような感じ。



main
.go
------

package main
import 
(
    
"hello_mod"
    "grumpy"
)
func main() {
    
:= grumpy.NewRootFrame()
    
mods:= grumpy.ImportModule(f"hello_mod", []*grumpy.Code{hello_mod.Code})
    
foo:= grumpy.GetAttr(fmods[0], grumpy.NewStr("say"), nil)
    
foo.Call(fnilnil)
}
 

grumpyパッケージは配置されているので、そのメソッド経由でコールすることができる。


まとめ


実際に使うことは無いかもだけど、pythonで書かれたモジュールをGoに変換して使うことができるのは便利かも。 もしdependencyなパッケージを含めて変換できたら、Goからsupervisorを使えたりするのは夢があると思う。覚えておいて損はないかも。 python3で書かれたやつも変換できるといいなぁ。


cgoでC/C++のコードを同居させられるし、色んな言語を混ぜ込んでGoが実行できるのは面白いかも。


ここ最近になってようやくこういうちょっと試すような時間が取れるようになったので、 コツコツやっていこうと思う。


現場からは以上です。

« 前の記事 次の記事 »

0件のコメント