Pythonista向けGo言語入門
Pythonista向けにGo言語入門を書いてみた。 Pythonならどう書くかわかってる人がgoで書く場合にどうすればいいのかをまとめました。
「python:」と「go:」の内容はできるだけ等価になるようにしてます。 表示のされかたとかは多少差はありますが、処理自体はより同じ意味になるように書いています。
配列の取り扱い
goのスライス型は一様な型を格納するので使い勝手としてはarray型のようなかんじ。
- python:
from array import array def main(): arr = array('i', [1, 2, 3]) arr.append(4) print arr # -> [1, 2, 3, 4] print arr[1:] # -> [2, 3, 4]
- go:
package main import ( "fmt" ) func main() { arr := []int{1,2,3} arr = append(arr, 4) fmt.Println(arr) // -> [1, 2, 3, 4] fmt.Println(arr[1:]) // -> [2, 3, 4] }
リストの取り扱い
goではなんでも受け取れる型として「interface{}」というのが使えます。 これをスライス型として定義すればPythonのlistオブジェクト風に使えます。
- python:
def main(): lst = [1, "hello", []] print lst # -> [1, "hello", []]
- go:
package main import ( "fmt" ) func main() { lst := []interface{}{1, "hello", []interface{}{}} fmt.Println(lst) // -> [1, hello, []] }
Anyという型を定義して以下のように書いてもOK
- go:
type Any interface{} func main() { lst := []Any{1, "hello!", []Any{}} fmt.Println(lst) }
なんだけど、Any値にアクセスする時はインターフェースキャストが必須。
- go:
item0 := lst[0].(int) item1 := lst[1].(string) item2 := lst[2].([]Any)
ちなみに「fmtのPrint*」関数はreflectをつかって中身を型認識して表示してくれてる。 要素の型を知らなくても処理できる実装を書く時はreflectパッケージの使い方を覚えましょう。
辞書の取り扱い
goではマップ型と呼ばれています。
- python:
def main(): d = { "key1": "hello", "key2": {"key1": "moge"}} print d
- go:
package main import ( "fmt" ) type Any interface{} func main() { d := map[string]Any{ "key1": "hello", "key2": map[string]Any{"key1": "moge"}} fmt.Println(d) // -> map[key1:hello key2:map[key1:moge]] }
Any値へのアクセスにはインターフェースキャストが必要なのは前述のとおり。
for構文について
pythonとはrangeの使い方が逆転してます。 連番の時はC言語ライクに書いて、イテレーション可能なものはrangeで一要素ごとに引き出します。
rangeは対象によって返す内容が違います。
- スライスの場合2値(インデックス, 要素)を返す。
- マップの場合2値(キー, 要素)を返す。
- チャネルの場合、受信した値をそのまま返す。
- python:
def main(): for i in range(10): print i lst = [1,3,5] for i in lst: print i for i, v in enumerate(lst): print i, v d = {"a": 1, "b": 3, "c": 5} for k, v in d.items(): print k,v
- go:
package main import "fmt" func main() { for i:=0; i<10; i++ { fmt.Println(i) } lst := []int{1, 3, 5} for _, i:= range(lst) { fmt.Println(i) } for i, v := range(lst) { fmt.Println(i, v) } d := map[string]int{"a": 1, "b": 3, "c": 5} for k, v := range(d) { fmt.Println(k, v) } }
ジェネレータについて
goでジェレータするにはgoroutinとchanを応用して作ります。
- python:
def gen(): for i in range(10): yield i def main(): for i in gen(): print i
- go:
package main import "fmt" func gen() chan int { ch := make(chan int) go func() { for i := 0; i < 10; i++ { ch <- i } close(ch) }() return ch } func main() { for i := range gen() { fmt.Println(i) } }
クラスについて
- python:
class Hoge(object): def __init__(self, sample): self.sample = sample def Get(self): return self.sample def main(): hoge = Hoge("hoge") print hoge print hoge.Get()
- go:
package main import "fmt" type Hoge struct { sample string } // コンストラクタはNew###という関数を作るのが習わしのようです。 func NewHoge(sample string) *Hoge { obj := new(Hoge) obj.sample = sample return obj } // Hogeインスタンスに対しメソッドGetを定義 // インスタンスをselfで受け取る様にも書けます。 func (self *Hoge) Get() string { return self.sample } func main() { hoge := NewHoge("hoge") fmt.Println(hoge) fmt.Println(hoge.Get()) }
Pythonと違って注意が必要なのは、
- フィールドやメソッドはシンボル先頭が大文字でないと他のパッケージから参照できない。
- つまり、このパッケージ内であれば、「hoge.sample」はアクセス可能だけど、他からは無理。
- メソッドをフラットに並べる。(コンストラクタの中でメソッド定義はできないみたい?)
- クラスの継承関係というのは記述できない。
- ミックスイン(他の定義と互換をもたせる)はできるそうです。
インターフェースについて
先ほどのクラス(構造体)定義をインターフェース経由でアクセスするようにすると 以下のようになります。
- go:
package main import "fmt" type Hoge struct { sample string } type Getter interface { Get() string } // コンストラクタはNew###という関数を作るのが習わしのようです。 func NewHoge(sample string) Getter { obj := new(Hoge) obj.sample = sample return Getter(obj) } // Hogeインスタンスに対しメソッドGetを定義 func (self *Hoge) Get() string { return self.sample } func main() { ihoge := NewHoge("hoge") fmt.Println(ihoge) fmt.Println(ihoge.Get()) }
Getterというインターフェースを追加定義して、NewHogeではインターフェースにキャストして返しています。 公開メソッド経由で操作するにはなんらふつーにインスタンスと扱いは同じ。 ただ、Getterインターフェースをカバーしていることがコンパイルに通る時点で保証されます。
Sleepさせるには?
- python:
import time def main(): print "1sec..." time.sleep(1.0) print "completed."
- go:
package main import ( "fmt" "time" ) func main() { fmt.Println("1sec...") time.Sleep(1*time.Second) fmt.Println("completed.") }
ちなみにgoのtimeには以下の定数が定義されています。
- go:
const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute )
今日の日付
goのtimeパッケージはタイムゾーンやUTC、賢いパーサーを最初から備えています。 pythonで同様のことをするには別途python-dateutilなどが必要になります。
- python:
from datetime import datetime, timedelta from dateutil import tz, parser def main(): now = datetime.now(tz=tz.gettz('Asia/Tokyo')) print now # -> 2013-05-14 09:15:48.587059+09:00 print now.astimezone(tz.tzutc()) # -> 2013-05-14 00:15:48.587059+00:00 print now + timedelta(days=3) # -> 2013-05-17 09:15:48.587059+09:00 print parser.parse("2013-05-14T12:42:53.473100+09:00") # -> 2013-05-14 12:42:53.473100+09:00
- go:
package main import ( "fmt" "time" ) func main() { now := time.Now() fmt.Println(now) // -> 2013-05-14 09:09:16.317452 +0900 JST fmt.Println(now.UTC()) // -> 2013-05-14 00:09:16.317452 +0000 UTC fmt.Println(now.Add(3*24*time.Hour)) // -> 2013-05-17 10:09:16.317452 +0900 JST tm, err := time.Parse(time.RFC3339, "2013-05-14T12:42:53.473100+09:00") fmt.Println(tm) // -> 2013-05-14 12:42:53.473100 +09:00 JST }
可変長引数の受け渡しと展開
goではキーワード引数の受け渡しや展開は無いっぽいが、 Pythonでいうところの「*args」のような機能はあります。 可変長引数を受け渡すとスライスになり、 スライスを展開して可変長引数として渡したりは出来るみたい。
- python:
def function1(*args): function2(*args) def function2(*args): print args, type(args) def main(): function1("hoge", "moge", "sample1") # -> ('hoge', 'moge', 'sample1') <type 'tuple'>
- go:
package main import ( "fmt" "reflect" ) func function1(args ...string) { function2(args...) } func function2(args ...string) { fmt.Println(args, reflect.TypeOf(args)) } func main() { function1("hoge", "moge", "sample1") // -> [hoge moge sample1] []string }
ただし、キーワード展開自動返値はある。 シンボル付きの返値を定義するとそのスコープにその変数定義が初期化済みになる。 returnのみ書けば、一式の値を多値として返すことが出来る。(順番は定義順) ちなみに多値返値は常に展開しようとする。 多値を多値のまま受け取れるtuple型はgolangには無いっぽい。
ここから先の柔軟な受け渡しが必要なら、 スライスを返したり構造体を返したりするように書くことになりそう。
- go:
package main import "fmt" func function1() (x, y int, value string) { x = 123 y = 333 value = "hello!" return } func function2(x, y int, value string) { fmt.Println(x, y, value) } func main() { fmt.Println(function1()) // -> 123 333 hello! function2(function1()) // -> 123 333 hello! }
以下の様に無名構造体定義を作って返すことは出来るよ! (そこから各値を取り出すのはキャスト必須になるけれど)
- go:
package main import "fmt" type Any interface{} func function1() Any { return struct { x, y int value string }{122, 333, "hello!"} } func main() { fmt.Println(function1()) // -> {122 333 hello!} }
子プロセスを起動してその出力を順次表示する
python版は試行錯誤中・・・。 こういうのPythonだと意外と大変。 gevent.subprocess とか使いたくなる。
- go:
package main import ( "bufio" "fmt" "io" "os" "os/exec" ) type Proc struct { *exec.Cmd Stdin chan string Stdout chan string Stderr chan string Done chan error } func inProc(ch chan string, in io.Writer) { for { s, ok := <-ch if !ok { return } if len(s) > 0 { in.Write([]byte(s)) } } } func outProc(ch chan string, out io.Reader) { reader := bufio.NewReader(out) for { line, _, err := reader.ReadLine() if err == io.EOF { return } ch <- string(line) } } func NewProc(exe string, args ...string) *Proc { self := &Proc{ exec.Command(exe, args...), make(chan string), make(chan string), make(chan string), make(chan error)} in, _ := self.StdinPipe() go func() { inProc(self.Stdin, in) }() out, _ := self.StdoutPipe() go func() { outProc(self.Stdout, out) }() err, _ := self.StderrPipe() go func() { outProc(self.Stderr, err) }() go func() { self.Done <- self.Run() close(self.Stdin) close(self.Stdout) close(self.Stderr) }() return self } func main() { proc := NewProc("cat", "exectest.go") for { select { case line, ok := <-proc.Stdout: if ok { fmt.Println(line) } case line, ok := <-proc.Stderr: if ok { fmt.Println("stderr:", line) } case err := <-proc.Done: fmt.Println("terminated.", err) if err == nil { os.Exit(0) } else { os.Exit(1) } return } } }
このコードには構造体定義にミックスインや、 chanを使った終了処理なども記述しているので参考にしてください。
Pythonのraw_inputと同等の処理
- python:
def main(): raw = raw_input("input:") print "read number", raw, "from stdin"
- go:
package main import "fmt" func raw_input(prompt string) string { var i string fmt.Print(prompt + " ") fmt.Scanln(&i) return i } func main() { raw := raw_input("input:") fmt.Println("read number", raw, "from stdin") }
簡易HTTPファイルサーバ
- python:
import SimpleHTTPServer import SocketServer def main(): Handler = SimpleHTTPServer.SimpleHTTPRequestHandler httpd = SocketServer.TCPServer(("", 8000), Handler) httpd.serve_forever()
- go:
package main import ( "net/http" ) func main() { http.Handle("/", http.FileServer(http.Dir("."))) http.ListenAndServe(":8000", nil) }
追記の予定
実験しててわかったことは今後もここに追記する予定ですー。 知りたいことがあれば以下のコメント欄に投げてみてください。