CUBE SUGAR CONTAINER

技術系のこと書きます。

Golang: math/big で大きい数や細かい数を扱う

Golang の数値型はビット数が決められているため、扱える数の大きさや精度に限界がある。

整数の大きさの限界

例えば 2 を 64 ビット左シフトした整数を作ってみる。

package main

import "fmt"

func main() {
    fmt.Println(2 << 64)
}

上記を実行しようとすると int 型で扱える範囲を越えているとコンパイラに怒られる。

$ go run overflow.go
# command-line-arguments
./overflow.go:6: constant 36893488147419103232 overflows int

整数で最も大きな数を扱える型は int64 なので 64 ビットで表現できる範囲に限られる。

より大きな数を扱う

じゃあ、それよりも大きな数を扱うにはどうしたら良いかというと多倍長整数を使えば良い。 Golang には math/lang パッケージに多倍長整数の Int がある。

多倍長整数を使って、先ほどと同じ内容の処理を書いてみよう。 Int 型のインスタンス x と y を作って Exp() メソッドで累乗を計算している。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewInt(2)
    y := big.NewInt(64 + 1)
    n := new(big.Int).Exp(x, y, nil)
    fmt.Println(n)
}

見たとおり計算には演算子を使う代わりにメソッドを使うことになる。

上記を実行してみる。

$ go run bigint.go
36893488147419103232

今度はオーバーフローしていない。

浮動小数点の精度の限界

同じように浮動小数点型には精度の限界がある。 例えば 1.0 を 3.0 で割ってみよう。

package main

import (
    "fmt"
)

func main() {
    x := 1.0
    y := 3.0
    f := x / y
    fmt.Println(f)
}

当然 1 を 3 で割り切ることはできないので、どこかで表現しきれなくなる。

$ go run floatprec.go 
0.3333333333333333

浮動小数点型は符号 * 仮数 * 基数 ^ 指数で表現される。 IEEE 754 では倍精度浮動小数点数は符号が 1 ビット、仮数が 52 ビット、指数が 11 ビットと決まっている。 つまり上記は 52 ビットの仮数で表現できる限界ということになる。 ちなみに基数は 2 で固定なのでビットを使うことはない。

より細かい数を扱う

浮動小数点でもっと精度の高い計算をしたいときは math/big に用意されている Float を使う。 この Float は仮数のビット数を任意に設定できる。

試しに先ほどの 1 / 3 の計算を 1024 ビットでやってみよう。 Float#SetPrec() が仮数を指定するためのメソッドになっている。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewFloat(1)
    y := big.NewFloat(3)
    f := new(big.Float).SetPrec(1024).Quo(x, y)
    fmt.Println(f)
}

実行すると先ほどよりなっがーーーい結果が表示される。

$ go run bigfloat.go
0.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333334

分数 (有理数) を扱う

先ほど見た通り浮動小数点で割り切れない数を扱おうとすると Float を使って高精度にはできたとしても限界はある。

そんなとき Rat を使えば分数を分数としてそのまま扱うことができる。 試しに (1 / 3) * (2 / 4) を計算してみよう。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewRat(1, 3)
    y := big.NewRat(2, 4)
    r := new(big.Rat).Mul(x, y)
    fmt.Println(r)
}

上記の結果は 2 / 12 で通分すると 1 / 6 になるはず。 実行すると、その通りの結果が得られる。

$ go run bigrat.go
1/6

まとめ

Golang で大きな数や細かい数や分数を扱いたいときは math/big パッケージを使おう。

Golang: gore でスニペットの動作を確認する

Golang でも REPL を使いたいときは gore というサードパーティ製のパッケージがある。

今回使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.4
BuildVersion:   15E65
$ go version 
go version go1.6 darwin/amd64

インストール

インストールは go get でさくさくと。

$ go get -u github.com/motemen/gore

インストールが終わると gore コマンドで REPL が立ち上がる。

$ gore
gore version 0.2.5  :help for help
gore>

使ってみる

あとは Python の REPL なんかと同じように一行ずつコードを入力していく。

gore> msg := "Hello, World!"
"Hello, World!"

注意点としてはパッケージをインポートするときは先頭にコロン (:) をつける必要があるところかな。

gore> :import fmt

それ以外だと fmt のプリント系関数を使うときも動作にクセがある。 ひとまず一旦 fmt.Println() してみよう。

gore> fmt.Println(msg)
Hello, World!
14
<nil>

そして、次はおもむろに変数に代入でもしてみる。

gore> n := 1
Hello, World!
1

なんかさっきと同じメッセージがまた表示されてる?!

実は、この動作は gore の内部実装と関係がある。 gore は内部的には入力された内容をひとつのファイルにマージした上で実行する作りになっているらしい。

例えば :print を入力すると、現在のソースコードの全体像が確認できる。

gore> :print
package main

import "fmt"

func __gore_p(xx ...interface{}) {
    for _, x := range xx {
        fmt.Printf("%#v\n", x)
    }
}
func main() { msg := "Hello, World!"; fmt.Println(msg); n := 1 }

ソースコード的には fmt.Println() したあとで変数 n に代入ことになっている。 そりゃ毎回メッセージが出るわけだね。

REPL で godoc を読む

godoc をインストールしておくと gore の REPL でドキュメントが読める。

$ go get -u golang.org/x/tools/cmd/godoc

:doc を入力してパッケージを指定すると、そのパッケージのドキュメントが表示される。

gore> :doc fmt

そんな感じでスニペットの動作をさくっと確認したいときはべんり。

Golang: gometalinter でソースコードを静的解析しまくる

先日、このブログで Golang の linter を使ってみたエントリを書いた。

blog.amedama.jp

すると @r_rudi さんから gometalinter という便利なものがあることを教えてもらえた。

何それ?

gometalinter は Golang の色々な linter を一度に実行できてしまう優れもの。

インストール

gometalinter は go get コマンドでさくっとインストールする。

$ go get github.com/alecthomas/gometalinter

初期設定

最初に gometalinter に --install と --update オプションをつけて実行する。 これで対応している linter をひと通りインストールしてくれる。

$ gometalinter --install --update
Installing:
  gotype
  aligncheck
  dupl
  interfacer
  golint
  structcheck
  ineffassign
  gosimple
  goimports
  errcheck
  unconvert
  goconst
  varcheck
  deadcode
  gocyclo
  lll

使ってみよう

ツッコミどころのあるソースコードを用意して動作を確認してみよう。

$ cat < 'EOF' > helloworld.go
package main

import (
    "fmt"
)

func main() {
    n := 1 // 使われない変数
    fmt.Println("Hello, World!")
}
EOF

上記のファイルを用意したのと同じディレクトリで gometalinter コマンドを実行する。

$ gometalinter .
helloworld.go:8:2:warning: n assigned and not used (ineffassign)
helloworld.go:8:2:error: n declared but not used (gotype)
helloworld.go:8:2:warning: n declared but not used (interfacer)
helloworld.go:8:2:warning: unused struct field n declared but not used (structcheck)
helloworld.go:8:2:warning: redundant type conversion (unconvert)
helloworld.go:8:2:warning: n declared but not used (aligncheck)

ありとあらゆる linter たちが一斉にソースコードにツッコミを入れてくれた。 指摘内容の右側のカッコに囲まれているのが、それを指摘した linter の名前になっている。

まとめ

  • gometalinter を使うと Golang の様々な linter を一度に実行できる

べんり。

Python: ast (Abstract Syntax Tree: 抽象構文木) モジュールについて

標準ライブラリの ast モジュールを使うと Python で書かれたソースコードを構文解析できる。 それによって得られるオブジェクトは AST (Abstract Syntax Tree: 抽象構文木) と呼ばれる。 その使い道としては、例えば Python の lint ツールなどが考えられる。 つまり、ソースコードの構造を確かめることでまずいところを見つけ出すことができる。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.4
BuildVersion:   15E65
$ python --version
Python 3.5.1

使ってみる

論よりコードということで実際にソースコードを ast モジュールで処理してみよう。

まずは次のようにシンプル極まりないサンプルを用意する。

$ cat << 'EOF' > helloworld.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-


def main():
    print('Hello, World!')


if __name__ == '__main__':
    main()
EOF

ここからは Python の REPL を使って動作確認をする。 先ほどのファイルと同じディレクトリで python コマンドを実行しよう。

$ python

まずは、先ほど用意したソースコードの内容を source という名前の変数に読み込んでおく。

>>> FILENAME = 'helloworld.py'
>>> with open(FILENAME, 'r') as f:
...     source = f.read()
...

読み込んだ内容を ast モジュールの parse() 関数に渡して構文解析しよう。

>>> import ast
>>> ast_object = ast.parse(source, FILENAME)

あるいは代わりに組み込み関数の compile() を使っても構わない。 実際のところ、上記でも内部的には以下を呼び出している。

>>> import ast
>>> ast_object = compile(source, FILENAME, 'exec', ast.PyCF_ONLY_AST)

すると ast モジュールの解析結果として Module オブジェクトが得られる。 これは Python モジュール (ようは *.py ファイル) に対応している。

>>> ast_object
<_ast.Module object at 0x103818e80>

これらのオブジェクトはノードと呼ばれる。

すべてのノードは ast.AST 抽象基底クラスのサブクラスになっている。

>>> isinstance(ast_object, ast.AST)
True

持っているアトリビュートはノードの種類によって異なる。 もし入れ子になった要素があるときは、それらがリストになった body というアトリビュートを持っている。

>>> ast_object.body
[<_ast.FunctionDef object at 0x10561d490>, <_ast.If object at 0x10561d590>]

上記ではモジュールが関数と if ステートメントをひとつずつ持っていることがノードの内容から判断できる。

関数や if ステートメントのノードには、それがソースコードの何行目にあるかという情報が lineno として得られる。

>>> ast_object.body[0].lineno
5
>>> ast_object.body[1].lineno
9

関数については名前の情報も name として得られる。 それに対し if ステートメントは名前がないので name アトリビュートもない。

>>> ast_object.body[0].name
'main'
>>> ast_object.body[1].name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'If' object has no attribute 'name'

ノードがどういったアトリビュートを持っているかは ast.dump() 関数を使うと分かりやすい。

>>> ast.dump(ast_object.body[0])
"FunctionDef(name='main', args=arguments(args=[], vararg=None, kwarg=None, defaults=[]), body=[Print(dest=None, values=[Str(s='Hello, World!')], nl=True)], decorator_list=[])"

全体の構造を出力してみる

先ほどは REPL を使って ast モジュールの基本的な使い方を確認した。 次は試しに AST の全体の構造を出力するプログラムを書いてみる。 先ほどの helloworld.py と同じディレクトリに用意しよう。

$ cat << 'EOF' > tree.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import ast


def walk(node, indent=0):
    # 入れ子構造をインデントで表現する
    print(' ' * indent, end='')

    # クラス名を表示する
    print(node.__class__, end='')

    # 行数の情報があれば表示する
    if hasattr(node, 'lineno'):
        msg = ': {lineno}'.format(lineno=node.lineno)
        print(msg, end='')

    # 改行を入れる
    print()

    # 再帰的に実行する
    for child in ast.iter_child_nodes(node):
        walk(child, indent=indent+4)


def main():
    FILENAME = 'helloworld.py'

    with open(FILENAME, 'r') as f:
        source = f.read()

    tree = ast.parse(source, FILENAME)
    walk(tree)


if __name__ == '__main__':
    main()
EOF

ast.iter_child_nodes() 関数を使ってノードの子要素を取り出して、それを再帰的に呼び出しながら情報を書き出している。 ノードの入れ子構造はインデントで表現した。

実行してみると helloworld.py の構造が表示される。 main 関数を表す FunctionDef の中に Print が入っていて、さらにその中には Str が入っていたり。 見ているだけで Python のソースコードは内部的にこう表現されているのかと面白い。

$ python tree.py
<class '_ast.Module'>
    <class '_ast.FunctionDef'>: 14
        <class '_ast.arguments'>
        <class '_ast.Print'>: 15
            <class '_ast.Str'>: 15
    <class '_ast.If'>: 28
        <class '_ast.Compare'>: 28
            <class '_ast.Name'>: 28
                <class '_ast.Load'>
            <class '_ast.Eq'>
            <class '_ast.Str'>: 28
        <class '_ast.Expr'>: 29
            <class '_ast.Call'>: 29
                <class '_ast.Name'>: 29
                    <class '_ast.Load'>

特定のノードを選択的に処理するには

先ほどの例ではノードを再帰的に処理する関数を自前で用意した。 実は ast にはそれ用のヘルパが用意されているので、そちらを使っても構わない。 具体的には ast.NodeVisitor クラスを継承してコールバックを実装する。 これは、例えば特定のノードだけを選択的に処理したいといったときは特に便利だ。

次のサンプルコードでは FunctionVisitor という関数だけを処理するクラスを定義している。 ast.NodeVisitor クラスを継承した上で、visit_ から始まるメソッドを実装する。 メソッド名の後ろには処理したいノードの名前を入れよう。 今回のように関数であれば FunctionDef になる。

$ cat << 'EOF' > visitor.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import ast


class FunctionVisitor(ast.NodeVisitor):

    def visit_FunctionDef(self, node):
        """FunctionDef オブジェクトだけに反応するコールバック"""
        print(node.name, end='')
        print(': ', end='')
        print(node.lineno)


def main():
    FILENAME = 'helloworld.py'

    with open(FILENAME, 'r') as f:
        source = f.read()

    tree = ast.parse(source, FILENAME)
    FunctionVisitor().visit(tree)


if __name__ == '__main__':
    main()
EOF

コールバックの中では関数の名前と、その関数が定義されている行数を出力している。

早速、先ほどのサンプルコードを実行してみる。

$ python visitor.py
main: 14

ばっちり main() 関数と、その場所が出力された。

いじょう。

Golang: ソースコードを静的に解析する

2016/4/5 追記: こっちを使った方が便利そうです。

blog.amedama.jp


今回は Golang でイケてるソースコードを書くのに使うと良さそうなツールについて調べた。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.4
BuildVersion:   15E65
$ go version
go version go1.6 darwin/amd64

標準のコンパイラ

まずは大前提として Golang は他の言語に比べると標準のコンパイラも結構細かくお作法について口出ししてくれる。

例えば、次のようにして使われない変数 n を残したソースコードを用意する。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
)

func main() {
  n := 1 // 使われない変数
  fmt.Println("Hello, World!")
}
EOF

これを go run コマンドでコンパイル&実行してみるとコンパイラがちゃんと注意してくれる。 オプションとかは特につけることなく厳しくチェックしてくれるのはとても心強い。

$ go run helloworld.go
# command-line-arguments
./helloworld.go:8: n declared and not used

親切だね。

go vet

とはいえコンパイラでは拾ってくれないものもある。

例えば、次のようにして到達しないコードが含まれるソースコードを用意してみよう。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
)

func main() {
  return
  fmt.Println("Hello, World!") // 到達しないコード
}
EOF

今度も go run コマンドで実行してみる。 するとエラーなどは特に出ることがなく実行できてしまった。

$ go run helloworld.go

こんなときは標準のツールとして用意されている go vet コマンドにかけてみよう。 すると、先ほどの問題の行を特定して教えてくれた。

$ go vet helloworld.go
helloworld.go:9: unreachable code
exit status 1

gofmt

また Golang は標準ツールとしてコーディングスタイルのチェッカが同梱されている。

例えば、次のようにインデントにスペースを使ったソースコードを用意しておこう。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
)

func main() {
  fmt.Println("Hello, World!")
}
EOF

これを gofmt コマンドにかけるとインデントがタブに変換されたものが標準出力として得られる。 Golang のコーディングスタイルではインデントが 8 タブに決まっているため。

$ gofmt helloworld.go
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, World!")
}

直接ファイルを上書きしてしまいたいときは -w オプションを付ける。 あるいは go fmt コマンドを使っても良さそう。

$ gofmt -w helloworld.go

golint

次に紹介するのは Golang 版の lint の golint だ。

これは標準で同梱されているツールではないので go get コマンドでインストールする。

$ go get -u github.com/golang/lint/golint

次は、一見すると何も問題がないようなソースコードを用意しておこう。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
)

func Greet() {
  fmt.Println("Hello, World!")
}

func main() {
  Greet()
}
EOF

これを golint にかけると公開されている関数 (先頭が大文字) にも関わらず godoc のコメントがないことで怒られる。

$ golint helloworld.go
helloworld.go:7:1: exported function Greet should have comment or be unexported

次はコメントをつけてみよう。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
)

// Greet は挨拶する関数です
func Greet() {
  fmt.Println("Hello, World!")
}

func main() {
  Greet()
}
EOF

今度は怒られない。

$ golint helloworld.go

goimports

Golang のコンパイラは使われていないインポートがあるときも激おこになる。

使われていないパッケージ os をインポートしたソースコードを用意しておこう。

$ cat << 'EOF' > helloworld.go
package main

import (
  "fmt"
  "os" // os パッケージは使われていない
)

// Greet は挨拶する関数です
func Greet() {
  fmt.Println("Hello, World!")
}

func main() {
  Greet()
}
EOF

もちろん、これは go run コマンドすると怒られる。

$ go run helloworld.go
# command-line-arguments
./helloworld.go:5: imported and not used: "os"

まあ、これは手動で削除すれば良いだけなんだけど、それをアシストしてくれる goimports というツールもある。 これも標準で同梱されていないのでインストールしよう。

$ go get golang.org/x/tools/cmd/goimports

先ほどのソースコードにかけると os が削除された内容が標準出力で得られる。 とはいえ、同じ行にあったコメントなんかは残るみたいだ。

$ goimports helloworld.go
package main

import "fmt"

// os パッケージは使われていない

// Greet は挨拶する関数です
func Greet() {
    fmt.Println("Hello, World!")
}

func main() {
    Greet()
}

ファイルを直接上書きしたいときは gofmt コマンドと同じように -w オプションをつける。

$ goimports -w helloworld.go

ちなみに goimports は不要なパッケージを削除するのとは反対に必要なパッケージを追加することもできる。

次のようにして fmt パッケージが足りていないソースコードを用意しておこう。

$ cat << 'EOF' > helloworld.go 
package main

// Greet は挨拶する関数です
func Greet() {
  fmt.Println("Hello, World!")
}

func main() {
  Greet()
}
EOF

これを goimports にかけると、今度は fmt パッケージが追加されている。

$ goimports helloworld.go 
package main

import "fmt"

// Greet は挨拶する関数です
func Greet() {
    fmt.Println("Hello, World!")
}

func main() {
    Greet()
}

べんり。

まとめ

今回は質の高い Golang のソースコードを書く助けになるツールをいくつか使ってみた。 ここではすべてコマンドラインでそれぞれのツールを実行したけど、実際にはエディタや CI と連携させて使うことになるはず。

Python: uptime でシステムの連続稼働時間を得る

システムの uptime (連続稼働時間) に関する情報はプラットフォームごとに取得する方法が異なっている。 今回紹介する Python の uptime パッケージは、それらの差異を吸収して共通の API で連続稼働時間を知ることができる。

今回使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.4
BuildVersion:   15E65
$ python --version
Python 3.5.1

インストール

uptime を pip コマンドでインストールする。

$ pip install uptime

使ってみる

使い方はシンプルこの上ないので REPL を使って示す。 python コマンドで起動しよう。

$ python

連続稼働時間は uptime モジュールの uptime() 関数で得られる。 単位は秒。

>>> import uptime
>>> uptime.uptime()
107.211436

ちなみに、もし連続稼働時間が不明なときには None が返るらしい。

複数回呼び出すと、もちろんだんだんと数値 (連続稼働時間) が増えていく。

>>> uptime.uptime()
111.069084
>>> uptime.uptime()
113.490662

また boottime() 関数では起動した時間を得ることができる。

>>> uptime.boottime()
datetime.datetime(2016, 3, 31, 21, 27, 5, 65)

シンプル!

Linux ディストリビューションのコマンド実行履歴について

Linux ディストリビューションのコマンド実行履歴まわりについて理解を深めるために動作を調べたのでまとめておく。 主にコマンド実行履歴を残すファイルの指定方法とか history コマンドについて。

今回使った環境は次の通り。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
$ uname -r
3.19.0-25-generic

コマンド実行履歴の保存先

まず、コマンド実行履歴が保存されるファイルの名前はシェル変数の HISTFILE で指定する。 シェル変数の一覧から HISTFILE を検索すると次のように見つけることができる。 また、保存する件数については HISTFILESIZE で指定するようだ。

$ set | grep HISTFILE
HISTFILE=/home/vagrant/.bash_history
HISTFILESIZE=2000

ちなみに、この変数は環境変数ではないらしい。 環境変数の一覧を検索しても見つからない。

$ env | grep HISTFILE

シェル変数と環境変数の違いについては、こちらのエントリに書いた。

blog.amedama.jp

シェル変数の HISTFILE で指定されているファイルを開くとコマンド履歴が残っている。 こうすれば直接 ~/.bash_history なんかを指定するよりも汎用性が高くなりそうだ。

$ tail -n 3 $HISTFILE
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get dist-upgrade -y
sudo shutdown -r now

上記を見ると、ここまで色々なコマンドを実行してきた割に、最後に実行したコマンドが shutdown のように見える。 これは実行したコマンドが HISTFILE へすぐには反映されないことが理由になっている。 実際に HISTFILE へ書き出されるのはログアウトのタイミングだ。

コマンド実行履歴の保存先を変更してみる

試しにコマンド実行履歴の保存先を変更してみよう。 これにはシェル変数の HISTFILE を変更する。

$ HISTFILE=/home/vagrant/.my_history

コマンド実行履歴が書き出されるように一旦ログアウトしよう。

$ exit

再度ログインすると、先ほど指定した名前のファイルができている。

$ ls -a ~/ | grep .my_history
.my_history

内容を見ると確かに先ほど実行した内容が保存されている。

$ tail -n 3 ~/.my_history
tail -n 3 $HISTFILE
HISTFILE=/home/vagrant/.my_history
exit

history コマンド

ファイルに書き出される前にここまで実行してきたコマンドの履歴を見るには history コマンドを使う。

$ history | tail -n 3
  179  ls -a ~/ | grep .my_history
  180  tail -n 3 ~/.my_history
  181  history | tail -n 3

履歴をすべて消去するには -c オプションをつけて実行しよう。

$ history -c

再度 history コマンドを実行すると履歴が消えていることがわかる。

$ history
    1  history

また、特定の履歴をピンポイントで削除するには -d オプションで履歴の番号を指定する。

$ history -d 1

再度 history コマンドを実行すると -d オプションをつけて実行する前にあったコマンドの履歴が消えている。

$ history
    1  history -d 1
    2  history

いじょう。