
この記事は、Zennで(無料)販売しているこちらの本のまとめ(チートシート的役割)として、執筆しました。
本記事では説明は省略しているので、ぜひ書籍もあわせてご覧ください。
PythonからGoへの移行で必要な知識を実践的なコード例とともに解説します。単純な対応表ではなく、なぜその書き方になるのか、どんな場面で使うのかまでまとめます。
この記事で分かること
- Pythonの書き方をGoで実現する具体的な方法
- 型システムの違いと実践的な対処法
- エラーハンドリングの考え方の違い
- 並行処理の書き方とパフォーマンス向上のコツ
変数宣言:型を意識した書き方
Pythonの動的な変数宣言
Pythonでは変数宣言が非常にシンプルです。
# Python x = 10 name = "Alice" numbers = [1, 2, 3]
Goの静的型付けによる宣言
Goでは型を意識した宣言が必要です。ただし、型推論により簡潔に書けます。
// Go - 型推論を使った短縮記法 x := 10 // int型として推論 name := "Alice" // string型として推論 numbers := []int{1, 2, 3} // int型のスライス // 明示的な型指定 var x int = 10 var name string = "Alice" var numbers []int = []int{1, 2, 3}
実践ポイント
- 関数内では
:=を使った短縮記法が一般的 - パッケージレベル(グローバル)変数は
varで宣言 - 型推論を活用してコードを簡潔に
複数変数の同時代入
Pythonの多重代入はGoでも同様に使えます:
# Python x, y = 1, 2 a, b = b, a # 値の交換
// Go x, y := 1, 2 a, b = b, a // 値の交換
条件分岐:ブロック構造の違い
Pythonのインデントベース
# Python if age >= 18: print("成人です") elif age >= 13: print("中高生です") else: print("子供です")
Goの波括弧ベース
// Go if age >= 18 { fmt.Println("成人です") } else if age >= 13 { fmt.Println("中高生です") } else { fmt.Println("子供です") }
Goの特殊な条件分岐:初期化付きif文
Goならではの便利な機能として、if文内で変数を初期化できます:
// Go特有の書き方 if age := 30; age >= 18 { fmt.Println("成人です") // ageはこのifブロック内でのみ有効 } // ファイル処理でよく使われるパターン if file, err := os.Open("data.txt"); err == nil { defer file.Close() // ファイル処理 } else { fmt.Printf("ファイルを開けません: %v\n", err) }
ループ:for文だけで全てを表現
Pythonの豊富なループ構文
# Python - 様々なループ for i in range(10): print(i) for item in items: print(item) for i, item in enumerate(items): print(f"{i}: {item}") while condition: # 処理 pass
Goのfor文による統一的な表現
Goではfor文一つで全てのループパターンを表現します。
// Go - C言語スタイルのfor文 for i := 0; i < 10; i++ { fmt.Println(i) } // range句を使った要素の反復 for _, item := range items { fmt.Println(item) } // インデックスと要素の両方を取得 for i, item := range items { fmt.Printf("%d: %v\n", i, item) } // while文相当 for condition { // 処理 } // 無限ループ for { // 処理 if shouldBreak { break } }
実践ポイント
_は使わない値を明示的に捨てる記号rangeはスライス、マップ、チャネルで使用可能break、continueはPythonと同じように使える
関数定義:戻り値と型の明示
Pythonの関数定義
# Python def greet(name: str) -> str: return f"Hello, {name}!" def calculate(x: int, y: int) -> tuple[int, int]: return x + y, x * y def process_data(**kwargs): # 可変キーワード引数 pass
Goの関数定義
// Go - 基本的な関数 func greet(name string) string { return fmt.Sprintf("Hello, %s!", name) } // 複数の戻り値 func calculate(x, y int) (int, int) { return x + y, x * y } // 名前付き戻り値 func divide(a, b float64) (result float64, err error) { if b == 0 { err = fmt.Errorf("division by zero") return } result = a / b return } // 可変引数 func sum(numbers ...int) int { total := 0 for _, n := range numbers { total += n } return total }
実践ポイント
- Goでは複数戻り値が一般的(特にエラーハンドリング)
- 可変引数は
...を使用 - 名前付き戻り値で可読性を向上
データ型:静的型付けの活用
基本型の扱い
# Python - 動的型付け number = 42 price = 99.99 name = "商品A" is_available = True
// Go - 型を意識した宣言 var number int = 42 var price float64 = 99.99 var name string = "商品A" var isAvailable bool = true // 型推論での簡潔な書き方 number := 42 price := 99.99 name := "商品A" isAvailable := true
リストとスライス
# Python - リスト fruits = ["apple", "banana", "orange"] fruits.append("grape") fruits.extend(["kiwi", "mango"]) sub_fruits = fruits[1:3]
// Go - スライス fruits := []string{"apple", "banana", "orange"} fruits = append(fruits, "grape") fruits = append(fruits, []string{"kiwi", "mango"}...) subFruits := fruits[1:3] // スライスの容量を指定した初期化 fruits := make([]string, 0, 10) // 長さ0、容量10
実践ポイント
- スライスは動的配列として使用
appendは新しいスライスを返すため、代入が必要makeでメモリ効率の良い初期化が可能
辞書とマップ
# Python - 辞書 user = {"name": "Alice", "age": 30} if "name" in user: print(user["name"]) for key, value in user.items(): print(f"{key}: {value}")
// Go - マップ user := map[string]interface{}{ "name": "Alice", "age": 30, } // 値の存在確認 if name, ok := user["name"]; ok { fmt.Println(name) } // 反復処理 for key, value := range user { fmt.Printf("%s: %v\n", key, value) } // 型安全なマップ(推奨) userInfo := map[string]string{ "name": "Alice", "email": "alice@example.com", }
文字列操作:stringsパッケージの活用
Pythonのメソッドチェーン
# Python text = " Hello, World! " result = text.strip().upper().replace("HELLO", "HI") words = result.split(", ") joined = " | ".join(words)
Goの関数ベース操作
// Go import "strings" text := " Hello, World! " trimmed := strings.TrimSpace(text) upper := strings.ToUpper(trimmed) replaced := strings.ReplaceAll(upper, "HELLO", "HI") words := strings.Split(replaced, ", ") joined := strings.Join(words, " | ") // チェーン風に書くための工夫 func processText(text string) string { text = strings.TrimSpace(text) text = strings.ToUpper(text) text = strings.ReplaceAll(text, "HELLO", "HI") return text }
実践ポイント
stringsパッケージの関数は純粋関数(元の文字列を変更しない)- 文字列は不変(immutable)
- パフォーマンスが重要な場合は
strings.Builderを使用
エラーハンドリング:例外ではなく戻り値
Pythonの例外処理
# Python try: with open("data.txt", "r") as file: content = file.read() result = process_data(content) print("処理完了") except FileNotFoundError: print("ファイルが見つかりません") except ValueError as e: print(f"データエラー: {e}") finally: print("クリーンアップ")
Goのエラー戻り値
// Go func processFile() error { file, err := os.Open("data.txt") if err != nil { return fmt.Errorf("ファイルを開けません: %w", err) } defer file.Close() // finallyの代わり content, err := io.ReadAll(file) if err != nil { return fmt.Errorf("読み込みエラー: %w", err) } result, err := processData(string(content)) if err != nil { return fmt.Errorf("データ処理エラー: %w", err) } fmt.Println("処理完了") return nil } // 呼び出し側 if err := processFile(); err != nil { log.Printf("エラー: %v", err) }
実践ポイント
- エラーは戻り値として明示的に処理
deferで確実なリソース管理- エラーのラップ(
%w)で詳細情報を保持
構造体:クラスの代替としての使い方
Pythonのクラス
# Python class User: def __init__(self, name: str, age: int): self.name = name self.age = age self._email = "" # プライベート風 def greet(self) -> str: return f"Hello, I'm {self.name}" @property def email(self) -> str: return self._email @email.setter def email(self, value: str): if "@" in value: self._email = value else: raise ValueError("Invalid email") user = User("Alice", 30) print(user.greet())
Goの構造体とメソッド
// Go type User struct { Name string Age int email string // 小文字で非公開 } // コンストラクタ関数 func NewUser(name string, age int) *User { return &User{ Name: name, Age: age, } } // メソッド func (u *User) Greet() string { return fmt.Sprintf("Hello, I'm %s", u.Name) } // ゲッター func (u *User) Email() string { return u.email } // セッター func (u *User) SetEmail(email string) error { if !strings.Contains(email, "@") { return fmt.Errorf("invalid email") } u.email = email return nil } // 使用例 user := NewUser("Alice", 30) if err := user.SetEmail("alice@example.com"); err != nil { log.Printf("Error: %v", err) } fmt.Println(user.Greet())
実践ポイント
- 構造体の大文字フィールドは公開、小文字は非公開
- レシーバーはポインタ型(
*User)を使うのが一般的 - コンストラクタ関数で初期化ロジックを管理
並行処理:ゴルーチンとチャネル
Pythonのスレッド処理
# Python import threading import queue import time def worker(q, results): while True: item = q.get() if item is None: break # 重い処理 time.sleep(1) results.put(f"Processed {item}") q.task_done() q = queue.Queue() results = queue.Queue() threads = [] for i in range(3): t = threading.Thread(target=worker, args=(q, results)) t.start() threads.append(t) # タスクを追加 for i in range(10): q.put(f"task-{i}") q.join() # スレッド終了 for i in range(3): q.put(None) for t in threads: t.join()
Goのゴルーチンとチャネル
// Go import ( "fmt" "sync" "time" ) func worker(id int, tasks <-chan string, results chan<- string, wg *sync.WaitGroup) { defer wg.Done() for task := range tasks { // 重い処理 time.Sleep(time.Second) results <- fmt.Sprintf("Worker %d processed %s", id, task) } } func processData() { tasks := make(chan string, 10) results := make(chan string, 10) var wg sync.WaitGroup // 3つのワーカーを起動 for i := 1; i <= 3; i++ { wg.Add(1) go worker(i, tasks, results, &wg) } // タスクを送信 go func() { for i := 0; i < 10; i++ { tasks <- fmt.Sprintf("task-%d", i) } close(tasks) // チャネルを閉じる }() // 結果を受信 go func() { wg.Wait() close(results) }() // 結果を出力 for result := range results { fmt.Println(result) } }
実践ポイント
- ゴルーチンは軽量スレッド(数万個でも起動可能)
- チャネルで安全なデータ共有
sync.WaitGroupで完了待機- チャネルのクローズで処理終了を通知
まとめ:PythonからGoへの移行のコツ
この記事では、PythonからGoへの移行で重要なポイントを実践的なコード例とともに解説しました。
重要な考え方の違い
- 型システム: 動的型付けから静的型付けへ
- エラーハンドリング: 例外から戻り値ベースへ
- 並行処理: スレッドからゴルーチンとチャネルへ
- メモリ管理: deferによる確実なクリーンアップ処理
PythonとGoそれぞれの良さを理解し、適切な場面で使い分けられるのが良さそうですね。