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)
}

追記の予定

実験しててわかったことは今後もここに追記する予定ですー。 知りたいことがあれば以下のコメント欄に投げてみてください。