この記事はGo3 Advent Calendar 2018の16日目の記事です。Golangで書かれたコードをpythonへ移植したときに得られた気づきを共有します。
背景
snakebiteというSpotifyで開発されたpure pythonのhdfsクライアントに copyFromLocal
の機能がなかったので自分で実装することにしました。
- snakebiteの代わりにlibhdfs3などを使えば高速に動作するフル機能が得られるが性能よりも依存関係の少なさを重視したかったのでsnakebiteに機能追加する方針にした
- HDFSにファイル作成するためのAPI呼び出しシーケンスを把握するのが面倒だったので既存の動いているコードから該当箇所を移植する方向で実装することにした
以上の方針から適当なhdfsクライアントを探した結果、golangで実装されている、colinmarc/hdfsがわかりやすくて必要な機能も実装されているようなのでcopyFromLocalに関連するコードを移植することにしました。
気づき
ここからは、この移植作業から得られた気づきを共有します。実際に、移植したpythonのコードはこちらです。
エラー処理をインターフェイスを変更することなく移植できる
golangでは正常な結果とエラーを多値として返すことが多いと思いますが、pythonではタプルを返すことで、制御構造をほぼ変更することなく移植することができます。
func (f *FileWriter) Write(b []byte) (int, error) {
/* ... */
n, _ := s.buf.Write(b)
err := s.flush(false)
return n, err
}
class FileWriter(object) {
def write(b):
#...
self.buf += b
err = self.flush(False)
return (len(b), err)
}
pythonっぽい書き方ではなくなりますが、書き換えがほぼ発生しないので移植作業にともなう不要なバグ埋めを減らせたように思います。
goroutine / channelのコードはそのまま移植できない
当たり前なんですが、そのまま移植できないのでその場に合わせて書き直す必要があります。例えば、
// Ack packets in the background.
go func() {
s.ackPackets()
close(s.acksDone)
}()
//...
func (s *blockStreamWriter)ackPackets() {
for {
s, ok := <-s.packets
if !ok {
return
}
//...
}
//...
}
のように、バックグラウンドでackの到着を監視するgoroutineがありました。今回は、パフォーマンスを無視してパケットを送信した直後にackの到着を待つ同期的な処理に書き直しました。
self._send_packet(packet)
err = self._wait_for_acks(packet) # ackパケットの到着を待つ
if err is not None:
# エラー処理
もちろん、スレッドと非同期IOを使えば完全に移植できると思いますが、とりあえず動かしたかったので今回はパフォーマンスよりも実装のシンプルさを選びました。非同期処理はやはりgolangが使いやすいと感じます。
まとめ
- golang -> pythonの移植は比較的やりやすい
- エラー処理をほぼそのまま移植できるので制御構造が大きく変わらなくて済む
- ただし、goroutine/channelを使っているコードはpythonに対応するような言語機能がないのでスレッドやライブラリを使う必要がある