フューチャー技術ブログ

PythonistaがGo言語に入門してみた

The Gopher character is based on the Go mascot designed by [Renée French](http://reneefrench.blogspot.com/).

はじめに

はじめまして。TIG DXユニット所属の村上です。2020年4月にフューチャーに新卒入社しました。

私の所属しているプロジェクトでは、Go言語を使ってWeb APIを構築しています。私は元々Pythonを主に書いていましたが、Go言語を書くのは初めてでした。そんな私がPythonからGo言語へ入門する際に、苦労した経験や発見を共有したいと思います!

自分のITスキルセット

  • プログラミング言語 : Python, Java, PHP, C, アセンブリ
  • データサイエンス : 確率統計, 機械学習(特に深層強化学習)
  • ハードウェア : コンピュータアーキテクチャ, 自作PC

研究室に配属されてからは機械学習に没頭していました。

PythonとGoの違い

始めに大まかな違いについて、PythonとGoを対比して見ていこうと思います。

項目 Python Go
言語仕様 オブジェクト指向 非オブジェクト指向
型付け 動的 静的
クラス
ポインタ
関数定義 def func
ブロック定義 インデント {}

私が初めて習得したプログラミング言語はCだったため、上記のGoにあってPythonにない仕様についてはスッと理解できました。Pythonしか書いたことが無かったらかなり苦労したかもしれません。

また、補足ですがPythonは動的型付け言語でありながらも、Python3以降は型ヒントという機能があります。これによってある程度型を明示できます。

PythonからGoに入門してハマったこと

本記事のメインコンテンツです。今後Go言語に入門する方の参考になれば幸いです。

1. ファイルのどこにも定義されていない変数が使われている

所属プロジェクトのGithubリポジトリにあるGoのプログラムを見た時、まず不思議に思ったことは、そのファイルのどこにも宣言されていない変数がシレっと使われているのを発見したことです。実際のものとは異なりますが、次のような状況でした。

package mypackage

func add() int {
a := b + c
return a
}

変数b, cがファイル内のどこにも定義されていません。なのにエラーにならないのです。

実はGoでは同じpackage内のファイルで宣言されている変数や構造体などを、そのpackageのファイル全体でそのまま使うことができるのです! 上記の例で説明すると、同じmypackage内の他のファイルの変数や構造体をそのまま使うことができます。

Pythonは他のファイルで宣言されている変数やクラスを参照するためには、そのファイルのimportが必要になります。この違いは想定外だったので、はじめはびっくりしました。

2. Goにインスタンスメソッドは存在しない

当たり前ですが、オブジェクト指向型言語ではないGoにはインスタンスの概念はありません。しかし、次のようなGoコードを見てください。

a := v.myfunc(b, c)

Pythonistaはこのようなコードを見せられると、インスタンスvのインスタンスメソッドmyfuncを呼び出しているようにしか見えません。Goはオブジェクト指向型言語ではないので、vはインスタンスではないのですが、Pythonが抜けきっていない私はvのクラスを特定するために、インスタンス化されている部分を必死に探しました。しかし、vは構造体です。構造体にメソッドや関数の定義など存在しません。

これはレシーバと呼ばれるもので、vという構造体に対してmyfuncという関数を実行できるものです。さらに、myfuncは1で説明した通り、同じpackage内の任意のファイルに定義できます。

func (v Mystruct) myfunc(a int, b int) int {
ret := a + b + v.sum
return ret
}

このように、vという構造体に対してのみ呼ぶことができる関数として定義され、vとmyfuncの引数を使って様々な処理を行うことができます。

3. 変数を宣言したら勝手に初期化される

Pythonでは変数の宣言だけ行うことはできず、必ず初期値を指定して初期化する必要があります。しかし、Goでは宣言ができます。宣言だけ行うことは他の多くの言語でもできますが、宣言だけして値を代入せずに使っている部分を発見しました。次のようなコードです。

var a int
var b bool

if !b {
fmt.Print(a)
}

Cを習得済みの私は焦りました。なぜならCでは変数を宣言した時点では、不定値が代入された状態になっているからです。なので上記のようなコードを見た瞬間、コードの動作に再現性が無いように見えました。

しかしGoでは変数を宣言した場合、デフォルトの初期値が勝手に代入される仕様になっています!

主要な型のデフォルト初期値は以下です。

初期値
int 0
bool false
float 0.0
string “”
ポインタ nil

このため、上記のコードでは0が出力されます。

4. bool値が微妙に違う

PythonからGoに入門する際に、最も大きな壁はここと言っても過言ではないかもしれません。

Pythonにおけるbool値はTrue, Falseです。しかし、Goはtrue, falseなんです。Goに入門してしばらく経ちますが、未だにTrue, Falseと書いてしまいます…最近では逆にPythonでtrue, falseと書いてしまうことも…

早く慣れたいです(泣)

5. 配列に対するappendの仕方が微妙に違う

Pythonでは配列(リスト)に対して要素を追加する際に、appendというメソッドを使います。使い方は次のような感じです。

a = []
a.append("apple")

一方Goでは、可変長配列(スライス)というものがあり、同じようにappendを使って要素を追加できるのですが、使い方が微妙に違います。

var a []string
a = append(a, "apple")

こちらも使い方がごちゃごちゃにならないように気を付けたいですね。

6. 変数の頭文字が大文字と小文字で意味が変わる

Pythonの場合、変数名や関数名が大文字だろうが小文字だろうが動作に関係ありません。しかし、Goでは変数名や関数名、その他もろもろの頭文字が大文字か小文字かによって、動作が異なります!

golang.jpに次のような記述があります。

Go言語の情報の可視性についてのルールは単純です。名前(トップレベルの型名、関数名、メソッド名、定数名、変数名、構造体のフィールドおよびメソッド名)の先頭一文字が大文字になっていれば、パッケージの利用者側から参照可能となります。すなわち大文字にしなければ、それが定義されているパッケージ内からしか参照できません。

つまり、Javaでいうところのprivateやpublicのような概念を、頭文字の大小で制御しています。実はこの仕様、気を付けないと危ない場面があります。例えばjsonをUnmarshalする時です。

Goでは次のように、構造体に対してjsonの中身を簡単に展開できます。

package main

import (
"encoding/json"
"fmt"
)

type PC struct {
Cpu string `json:"cpu"`
Gpu string `json:"gpu"`
Memory int `json:"memory"`
}
func main() {
spec := `{"cpu":"6700K", "gpu":"2080Ti", "memory":16}`
p := new(PC)
_ = json.Unmarshal([]byte(spec), p)
fmt.Printf("%+v", p)
// => &{Cpu:6700K Gpu:2080Ti Memory:16}
}

しかし、次のように構造体のフィールド名の頭文字を小文字にすると、外部パッケージから参照できなくなるため、3で説明した初期値になってしまいます。
ここを間違えると意図しない動作になってしまうので、気を付けたいところです。

package main

import (
"encoding/json"
"fmt"
)

type PC struct {
cpu string `json:"cpu"`
gpu string `json:"gpu"`
memory int `json:"memory"`
}
func main() {
spec := `{"cpu":"6700K", "gpu":"2080Ti", "memory":16}`
p := new(PC)
_ = json.Unmarshal([]byte(spec), p)
fmt.Printf("%+v", p)
// => &{cpu: gpu: memory:0}
}

オススメの記事

Goを学ぶときにつまずきやすいポイントFAQ という記事が非常に参考になります。

おわりに

今回はPythonをメインで触ってきた私がGoに入門して、躓いたことや苦労した部分をまとめてみました。

他にも、変数の後ろに型を書いて宣言するなど、Goには割と独特な仕様が多い印象です。

自分と同じように、これからGoを始める人の参考になれば幸いです!